Source release v3.0.2

This commit is contained in:
Joey Parrish
2015-10-08 16:57:59 -07:00
parent b5d6be97cb
commit 7a64ef6641
43 changed files with 644 additions and 7803 deletions

6
.gitignore vendored
View File

@@ -1,5 +1,3 @@
_auto_*
# GYP-generated files.
Makefile
*.Makefile
@@ -14,3 +12,7 @@ out/
# Ignoring backup files.
*~
# Configure logs from protobuf.
config.status
config.log

Binary file not shown.

View File

@@ -88,6 +88,7 @@ def ImportPlatform(name, gyp_args):
if hasattr(target, 'export_variables'):
for k, v in target.export_variables.iteritems():
if not os.environ.get(k):
os.environ[k] = v
print ' set %s to %s' % (k, v)

View File

@@ -130,6 +130,20 @@ class CDM_EXPORT Cdm : public ITimerClient {
// See Cdm::createSession().
class IEventListener {
public:
// A URL to be added to a renewal request message.
// This call will immediately precede the onMessage() call.
// Do not override this call if the URL is not needed.
//
// WARNING: this call exists temporarily to allow interoperation with
// older versions of Chromium and the prefixed EME API. This call will
// be removed in a future release. Therefore: (1) Do not use this call
// unless you are certain that it is needed on your platform for your
// application, and (2) If it is needed, figure how move to a new version
// of Chromium and the unprefixed EME API as soon as possible.
// TODO: Remove this call (see b/24776024).
virtual void onMessageUrl(const std::string& session_id,
const std::string& server_url) {}
// A message (license request, renewal, etc.) to be dispatched to the
// application's license server.
// The response, if successful, should be provided back to the CDM via a

View File

@@ -1,2 +1,2 @@
// Widevine CE CDM Version
#define CDM_VERSION "v3.0.1-0-g41710d9-ce"
#define CDM_VERSION "v3.0.2-0-g161de1b-ce"

View File

@@ -288,13 +288,20 @@ Cdm::Status CdmImpl::generateRequest(const std::string& session_id,
return kInvalidAccess;
}
InitializationData init_data_obj(init_data_type_name, init_data);
if (init_data_obj.IsEmpty()) {
LOGE("Failed to parse init data.");
if (init_data.empty()) {
LOGE("Empty init data is not valid.");
return kInvalidAccess;
}
InitializationData init_data_obj(init_data_type_name, init_data);
if (init_data_obj.IsEmpty()) {
// Note that InitializationData's idea of "empty" includes "failed to find
// and parse a Widevine PSSH". This should not happen for WebM init data,
// which requires no parsing.
LOGE("Failed to parse init data, may not contain a Widevine PSSH.");
return kNotSupported;
}
std::string key_request;
CdmKeyRequestType key_request_type;
std::string ignored_server_url;
@@ -404,6 +411,8 @@ Cdm::Status CdmImpl::update(const std::string& session_id,
bool predicted_to_be_server_cert_response =
property_set_.use_privacy_mode() &&
property_set_.service_certificate().empty();
(void)predicted_to_be_server_cert_response;
// predicted_to_be_server_cert_response is now used when assertions are off.
// NOTE: If the CdmSession object recognizes that this is not the first
// AddKey(), it will internally delegate to RenewKey().
@@ -640,6 +649,11 @@ void CdmImpl::OnSessionRenewalNeeded(const CdmSessionId& session_id) {
LOGI("A license renewal has been generated.");
MessageType message_type = kLicenseRenewal;
// Post the server_url before providing the message.
// For systems that still require the server URL,
// the listener will add the URL to its renewal request.
listener_->onMessageUrl(session_id, server_url);
listener_->onMessage(session_id, message_type, message);
}
@@ -650,7 +664,6 @@ void CdmImpl::OnSessionKeysChange(const CdmSessionId& session_id,
CdmKeyStatusMap::const_iterator it;
for (it = keys_status.begin(); it != keys_status.end(); ++it) {
KeyStatus status;
switch (it->second) {
case kKeyStatusUsable:
map[it->first] = kUsable;
@@ -835,7 +848,8 @@ int64_t Clock::GetCurrentTime() {
return host.clock->now() / 1000;
}
struct File::Impl {
class File::Impl {
public:
std::string name;
bool read_only;
bool truncate;

View File

@@ -8,7 +8,8 @@
namespace wvcdm {
struct Lock::Impl {
class Lock::Impl {
public:
pthread_mutex_t mutex;
};

View File

@@ -76,6 +76,16 @@ const std::string kCencPersistentInitData = a2bs_hex(
// pssh data:
"08011a0d7769646576696e655f746573"
"74220d6f66666c696e655f636c697032");
const std::string kInvalidCencInitData = a2bs_hex(
"0000000c" // blob size
"61736466" // "asdf" (wrong box type)
"01020304"); // nonsense
const std::string kNonWidevineCencInitData = a2bs_hex(
"00000020" // blob size
"70737368" // "pssh"
"00000000" // flags
"000102030405060708090a0b0c0d0e0f" // unknown system id
"00000000"); // pssh data size
const std::string kWebMInitData = a2bs_hex("deadbeefdeadbeefdeadbeefdeadbeef");
const std::string kKeyIdsInitData =
"{\"kids\":[\"67ef0gd8pvfd0\",\"77ef0gd8pvfd0\"]}";
@@ -582,6 +592,23 @@ TEST_F(CdmTest, GenerateRequest) {
EXPECT_EQ(Cdm::kInvalidAccess, status);
Mock::VerifyAndClear(this);
// Try to pass invalid CENC init data.
status = cdm_->createSession(Cdm::kTemporary, &session_id);
ASSERT_EQ(Cdm::kSuccess, status);
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).Times(0);
status = cdm_->generateRequest(session_id, Cdm::kCenc, kInvalidCencInitData);
EXPECT_EQ(Cdm::kNotSupported, status);
Mock::VerifyAndClear(this);
// Try to pass non-Widevine CENC init data.
status = cdm_->createSession(Cdm::kTemporary, &session_id);
ASSERT_EQ(Cdm::kSuccess, status);
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).Times(0);
status = cdm_->generateRequest(session_id, Cdm::kCenc,
kNonWidevineCencInitData);
EXPECT_EQ(Cdm::kNotSupported, status);
Mock::VerifyAndClear(this);
// Try a bogus session ID.
EXPECT_CALL(*this, onMessage(_, _, _)).Times(0);
status = cdm_->generateRequest(kBogusSessionId, Cdm::kCenc, kCencInitData);

View File

@@ -72,6 +72,7 @@ int main(int argc, char** argv) {
Cdm::Status status = Cdm::initialize(
Cdm::kNoSecureOutput, client_info, g_host, g_host, g_host, &cert_request,
static_cast<Cdm::LogLevel>(verbosity));
(void)status; // status is now used when assertions are turned off.
assert(status == Cdm::kSuccess);
assert(cert_request.needed == false);

View File

@@ -26,7 +26,8 @@ class BufferReader {
BufferReader(const uint8_t* buf, size_t size)
: buf_(buf), size_(buf != NULL ? size : 0), pos_(0) {}
bool HasBytes(size_t count) { return (pos() + count <= size()); }
bool HasBytes(size_t count) const { return pos_ + count <= size_; }
bool IsEOF() const { return pos_ >= size_; }
// Read a value from the stream, performing endian correction,
// and advance the stream pointer.

View File

@@ -100,7 +100,8 @@ class CdmEngine {
// Query system information
virtual CdmResponseType QueryStatus(SecurityLevel security_level,
CdmQueryMap* info);
const std::string& key,
std::string* value);
// Query session information
virtual CdmResponseType QuerySessionStatus(const CdmSessionId& session_id,

View File

@@ -136,6 +136,7 @@ class DeviceFiles {
FRIEND_TEST(WvCdmRequestLicenseTest, UnprovisionTest);
FRIEND_TEST(WvCdmRequestLicenseTest, ForceL3Test);
FRIEND_TEST(WvCdmRequestLicenseTest, UsageInfoRetryTest);
FRIEND_TEST(WvCdmRequestLicenseTest, UsageReleaseAllTest);
FRIEND_TEST(WvCdmUsageInfoTest, UsageInfo);
FRIEND_TEST(WvCdmUsageTest, WithClientId);
FRIEND_TEST(WvCdmExtendedDurationTest, UsageOverflowTest);

View File

@@ -85,6 +85,8 @@ class PolicyEngine {
bool IsLicenseOrPlaybackDurationExpired(int64_t current_time);
bool CanRenew() { return policy_.can_renew(); }
private:
friend class PolicyEngineTest;

View File

@@ -203,8 +203,10 @@ enum CdmResponseType {
LICENSE_REQUEST_NONCE_GENERATION_ERROR,
LICENSE_REQUEST_SIGNING_ERROR,
EMPTY_LICENSE_REQUEST,
EMPTY_PROVISIONING_CERTIFICATE_2,
SECURE_BUFFER_REQUIRED,
DUPLICATE_SESSION_ID_SPECIFIED,
LICENSE_RENEWAL_PROHIBITED,
EMPTY_PROVISIONING_CERTIFICATE_2,
};
enum CdmKeyStatus {

View File

@@ -405,91 +405,115 @@ CdmResponseType CdmEngine::RenewKey(const CdmSessionId& session_id,
}
CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level,
CdmQueryMap* key_info) {
const std::string& key,
std::string* value) {
LOGI("CdmEngine::QueryStatus");
CryptoSession crypto_session;
if (security_level == kLevel3) {
CdmResponseType status = crypto_session.Open(kLevel3);
if (NO_ERROR != status) return INVALID_QUERY_STATUS;
}
switch (crypto_session.GetSecurityLevel()) {
if (key == QUERY_KEY_SECURITY_LEVEL) {
CdmSecurityLevel security_level = crypto_session.GetSecurityLevel();
switch (security_level) {
case kSecurityLevelL1:
(*key_info)[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L1;
*value = QUERY_VALUE_SECURITY_LEVEL_L1;
break;
case kSecurityLevelL2:
(*key_info)[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L2;
*value = QUERY_VALUE_SECURITY_LEVEL_L2;
break;
case kSecurityLevelL3:
(*key_info)[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L3;
*value = QUERY_VALUE_SECURITY_LEVEL_L3;
break;
case kSecurityLevelUninitialized:
case kSecurityLevelUnknown:
(*key_info)[QUERY_KEY_SECURITY_LEVEL] =
QUERY_VALUE_SECURITY_LEVEL_UNKNOWN;
*value = QUERY_VALUE_SECURITY_LEVEL_UNKNOWN;
break;
default:
return INVALID_QUERY_KEY;
LOGW("CdmEngine::QueryStatus: Unknown security level: %d",
security_level);
return UNKNOWN_ERROR;
}
} else if (key == QUERY_KEY_DEVICE_ID) {
std::string deviceId;
bool success = crypto_session.GetDeviceUniqueId(&deviceId);
if (success) {
(*key_info)[QUERY_KEY_DEVICE_ID] = deviceId;
if (!crypto_session.GetDeviceUniqueId(&deviceId)) {
LOGW("CdmEngine::QueryStatus: GetDeviceUniqueId failed");
return UNKNOWN_ERROR;
}
*value = deviceId;
} else if (key == QUERY_KEY_SYSTEM_ID) {
uint32_t system_id;
success = crypto_session.GetSystemId(&system_id);
if (success) {
if (!crypto_session.GetSystemId(&system_id)) {
LOGW("CdmEngine::QueryStatus: GetSystemId failed");
return UNKNOWN_ERROR;
}
std::ostringstream system_id_stream;
system_id_stream << system_id;
(*key_info)[QUERY_KEY_SYSTEM_ID] = system_id_stream.str();
}
*value = system_id_stream.str();
} else if (key == QUERY_KEY_PROVISIONING_ID) {
std::string provisioning_id;
success = crypto_session.GetProvisioningId(&provisioning_id);
if (success) {
(*key_info)[QUERY_KEY_PROVISIONING_ID] = provisioning_id;
if (!crypto_session.GetProvisioningId(&provisioning_id)) {
LOGW("CdmEngine::QueryStatus: GetProvisioningId failed");
return UNKNOWN_ERROR;
}
*value = provisioning_id;
} else if (key == QUERY_KEY_CURRENT_HDCP_LEVEL ||
key == QUERY_KEY_MAX_HDCP_LEVEL) {
CryptoSession::HdcpCapability current_hdcp;
CryptoSession::HdcpCapability max_hdcp;
success = crypto_session.GetHdcpCapabilities(&current_hdcp, &max_hdcp);
if (success) {
(*key_info)[QUERY_KEY_CURRENT_HDCP_LEVEL] = MapHdcpVersion(current_hdcp);
(*key_info)[QUERY_KEY_MAX_HDCP_LEVEL] = MapHdcpVersion(max_hdcp);
if (!crypto_session.GetHdcpCapabilities(&current_hdcp, &max_hdcp)) {
LOGW("CdmEngine::QueryStatus: GetHdcpCapabilities failed");
return UNKNOWN_ERROR;
}
*value = MapHdcpVersion(key == QUERY_KEY_CURRENT_HDCP_LEVEL ? current_hdcp
: max_hdcp);
} else if (key == QUERY_KEY_USAGE_SUPPORT) {
bool supports_usage_reporting;
success = crypto_session.UsageInformationSupport(&supports_usage_reporting);
if (success) {
(*key_info)[QUERY_KEY_USAGE_SUPPORT] =
supports_usage_reporting ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE;
if (!crypto_session.UsageInformationSupport(&supports_usage_reporting)) {
LOGW("CdmEngine::QueryStatus: UsageInformationSupport failed");
return UNKNOWN_ERROR;
}
*value = supports_usage_reporting ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE;
} else if (key == QUERY_KEY_NUMBER_OF_OPEN_SESSIONS) {
size_t number_of_open_sessions;
success = crypto_session.GetNumberOfOpenSessions(&number_of_open_sessions);
if (success) {
if (!crypto_session.GetNumberOfOpenSessions(&number_of_open_sessions)) {
LOGW("CdmEngine::QueryStatus: GetNumberOfOpenSessions failed");
return UNKNOWN_ERROR;
}
std::ostringstream open_sessions_stream;
open_sessions_stream << number_of_open_sessions;
(*key_info)[QUERY_KEY_NUMBER_OF_OPEN_SESSIONS] =
open_sessions_stream.str();
*value = open_sessions_stream.str();
} else if (key == QUERY_KEY_MAX_NUMBER_OF_SESSIONS) {
size_t maximum_number_of_sessions;
if (!crypto_session.GetMaxNumberOfSessions(&maximum_number_of_sessions)) {
LOGW("CdmEngine::QueryStatus: GetMaxNumberOfOpenSessions failed");
return UNKNOWN_ERROR;
}
size_t maximum_number_of_sessions;
success = crypto_session.GetMaxNumberOfSessions(&maximum_number_of_sessions);
if (success) {
std::ostringstream max_sessions_stream;
max_sessions_stream << maximum_number_of_sessions;
(*key_info)[QUERY_KEY_MAX_NUMBER_OF_SESSIONS] =
max_sessions_stream.str();
*value = max_sessions_stream.str();
} else if (key == QUERY_KEY_OEMCRYPTO_API_VERSION) {
uint32_t api_version;
if (!crypto_session.GetApiVersion(&api_version)) {
LOGW("CdmEngine::QueryStatus: GetApiVersion failed");
return UNKNOWN_ERROR;
}
uint32_t api_version;
success = crypto_session.GetApiVersion(&api_version);
if (success) {
std::ostringstream api_version_stream;
api_version_stream << api_version;
(*key_info)[QUERY_KEY_OEMCRYPTO_API_VERSION] = api_version_stream.str();
*value = api_version_stream.str();
} else {
LOGW("CdmEngine::QueryStatus: Unknown status requested, key = %s",
key.c_str());
return INVALID_QUERY_KEY;
}
return NO_ERROR;
@@ -844,6 +868,11 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id,
}
CdmResponseType CdmEngine::ReleaseAllUsageInfo(const std::string& app_id) {
if (NULL == usage_property_set_.get()) {
usage_property_set_.reset(new UsagePropertySet());
}
usage_property_set_->set_app_id(app_id);
CdmResponseType status = NO_ERROR;
for (int j = kSecurityLevelL1; j < kSecurityLevelUnknown; ++j) {
DeviceFiles handle;
@@ -854,6 +883,14 @@ CdmResponseType CdmEngine::ReleaseAllUsageInfo(const std::string& app_id) {
"stops", j);
status = RELEASE_ALL_USAGE_INFO_ERROR_1;
} else {
SecurityLevel security_level =
static_cast<CdmSecurityLevel>(j) == kSecurityLevelL3
? kLevel3
: kLevelDefault;
usage_property_set_->set_security_level(security_level);
usage_session_.reset(
new CdmSession(usage_property_set_.get(),
EMPTY_ORIGIN, NULL, NULL));
CdmResponseType status2 = usage_session_->
DeleteMultipleUsageInformation(provider_session_tokens);
if (status2 != NO_ERROR) {
@@ -866,6 +903,7 @@ CdmResponseType CdmEngine::ReleaseAllUsageInfo(const std::string& app_id) {
status = RELEASE_ALL_USAGE_INFO_ERROR_2;
}
}
usage_session_.reset(NULL);
return status;
}

View File

@@ -48,6 +48,7 @@ CdmSession::CdmSession(CdmClientPropertySet* cdm_client_property_set,
key_set_id_ = *forced_session_id;
} else {
bool ok = GenerateKeySetId(&key_set_id_);
(void)ok; // ok is now used when assertions are turned off.
assert(ok);
}
session_id_ = key_set_id_;

View File

@@ -641,6 +641,11 @@ CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) {
buffer_descriptor.type =
params.is_secure ? destination_buffer_type_ : OEMCrypto_BufferType_Clear;
if (params.is_secure &&
buffer_descriptor.type == OEMCrypto_BufferType_Clear) {
return SECURE_BUFFER_REQUIRED;
}
switch (buffer_descriptor.type) {
case OEMCrypto_BufferType_Clear:
buffer_descriptor.buffer.clear.address =

View File

@@ -55,7 +55,7 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data,
// (optional, if version == 1) K * 16 byte key ID.
// 4 byte size of PSSH data, exclusive. (N)
// N byte PSSH data.
while (1) {
while (!reader.IsEOF()) {
size_t start_pos = reader.pos();
// atom size, used for skipping.
@@ -128,6 +128,7 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data,
"the atom.");
return false;
}
LOGV("CdmEngine::ExtractWidevinePssh: Skipping non-Widevine PSSH.");
continue;
}

View File

@@ -327,6 +327,11 @@ CdmResponseType CdmLicense::PrepareKeyUpdateRequest(
return INVALID_PARAMETERS_LIC_2;
}
if (is_renewal && !policy_engine_->CanRenew()) {
LOGE("CdmLicense::PrepareKeyUpdateRequest: license renewal prohibited");
return LICENSE_RENEWAL_PROHIBITED;
}
LicenseRequest license_request;
if (is_renewal)
license_request.set_type(LicenseRequest::RENEWAL);

View File

@@ -0,0 +1,374 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Description:
// Privacy crypto implementation for iOS. This fully implements the
// privacy_crypto methods. This assumes this is compiled on a Mac and is
// being compiled for iOS. Requires iOS 2.0 or later.
//
// This is never included in the default builds. If compiling using the gyp
// files, setting privacy_crypto_impl to "apple" with use this file rather
// than the openssl version.
#include "privacy_crypto.h"
#include <CommonCrypto/CommonCryptor.h>
#include <CommonCrypto/CommonDigest.h>
#include <CoreFoundation/CoreFoundation.h>
#include <memory>
#include <Security/Security.h>
#include <Security/SecKey.h>
#include "string_conversions.h"
#include "log.h"
#define KEYSTORE_NAME "com.google.widevine.publicKey"
namespace {
const int kPssSaltLength = 20;
const int kOaepMinPadding = 2 * CC_SHA1_DIGEST_LENGTH + 1;
template<typename T>
struct CFDeleter {
void operator()(T arg) {
CFRelease(arg);
}
};
template<typename T>
using CF =
std::unique_ptr<typename std::remove_pointer<T>::type, CFDeleter<T> >;
SecKeyRef ImportPublicKey(const std::string& key) {
std::string peerStr = KEYSTORE_NAME;
CF<CFDataRef> peerData(CFDataCreate(NULL,
reinterpret_cast<const UInt8*>(peerStr.c_str()), peerStr.length()));
CF<CFDataRef> keyData(CFDataCreate(NULL,
reinterpret_cast<const UInt8*>(key.c_str()), key.length()));
// Create a selector and delete all old keys.
CF<CFMutableDictionaryRef> deleteAttributes(CFDictionaryCreateMutable(
NULL, 0, NULL, NULL));
CFDictionarySetValue(deleteAttributes.get(), kSecClass, kSecClassKey);
CFDictionarySetValue(deleteAttributes.get(), kSecAttrKeyType,
kSecAttrKeyTypeRSA);
CFDictionarySetValue(deleteAttributes.get(), kSecAttrApplicationTag,
peerData.get());
SecItemDelete(deleteAttributes.get());
// Create attributes to add to the keystore.
CF<CFMutableDictionaryRef> addAttributes(CFDictionaryCreateMutable(
NULL, 0, NULL, NULL));
CFDictionarySetValue(addAttributes.get(), kSecClass, kSecClassKey);
CFDictionarySetValue(addAttributes.get(), kSecAttrKeyType,
kSecAttrKeyTypeRSA);
CFDictionarySetValue(addAttributes.get(), kSecAttrApplicationTag,
peerData.get());
CFDictionarySetValue(addAttributes.get(), kSecValueData,
keyData.get());
CFDictionarySetValue(addAttributes.get(), kSecAttrKeyClass,
kSecAttrKeyClassPublic);
CFDictionarySetValue(addAttributes.get(), kSecReturnPersistentRef,
kCFBooleanTrue);
// Add the key to the keystore.
CFTypeRef temp;
OSStatus status = SecItemAdd(addAttributes.get(), &temp);
CF<CFTypeRef> peer(temp);
if (!peer || (status != noErr && status != errSecDuplicateItem)) {
LOGE("RsaPublicKey::Init: Error adding key to keychain %d", status);
return NULL;
}
// Create attributes for for the query.
CF<CFMutableDictionaryRef> queryAttributes(CFDictionaryCreateMutable(
NULL, 0, NULL, NULL));
CFDictionarySetValue(queryAttributes.get(), kSecClass, kSecClassKey);
CFDictionarySetValue(queryAttributes.get(), kSecAttrApplicationTag,
peerData.get());
CFDictionarySetValue(queryAttributes.get(), kSecAttrKeyType,
kSecAttrKeyTypeRSA);
CFDictionarySetValue(queryAttributes.get(), kSecAttrKeyClass,
kSecAttrKeyClassPublic);
CFDictionarySetValue(queryAttributes.get(), kSecReturnRef, kCFBooleanTrue);
// Query the keychain to get the public key ref.
CFTypeRef keyRef = NULL;
status = SecItemCopyMatching(queryAttributes.get(), &keyRef);
if (status != noErr) {
LOGE("RsaPublicKey::Init: Error getting key from keystore %d", status);
return NULL;
}
return reinterpret_cast<SecKeyRef>(const_cast<void*>(keyRef));
}
// Apply a custom mask generation function (MGF) using the hash function SHA1,
// this is from OpenSSL.
void ApplyMGF1_SHA1(uint8_t *output, size_t outputLength,
const uint8_t* seed, size_t seedLength) {
size_t outputIndex = 0;
for (int i = 0; outputIndex < outputLength; i++) {
uint8_t extra[4];
extra[0] = (uint8_t)((i >> 24) & 0xFF);
extra[1] = (uint8_t)((i >> 16) & 0xFF);
extra[2] = (uint8_t)((i >> 8) & 0xFF);
extra[3] = (uint8_t)(i & 0xFF);
CC_SHA1_CTX ctx;
CC_SHA1_Init(&ctx);
CC_SHA1_Update(&ctx, seed, seedLength);
CC_SHA1_Update(&ctx, extra, 4);
if (outputIndex + CC_SHA1_DIGEST_LENGTH <= outputLength) {
CC_SHA1_Final(output + outputIndex, &ctx);
} else {
uint8_t temp[CC_SHA1_DIGEST_LENGTH];
CC_SHA1_Final(temp, &ctx);
memcpy(output + outputIndex, temp, outputLength - outputIndex);
}
outputIndex += CC_SHA1_DIGEST_LENGTH;
}
}
std::string ApplyOAEPPadding(const std::string& messageStr, size_t rsaSize) {
if (messageStr.length() > rsaSize - kOaepMinPadding ) {
LOGE("RsaPublicKey::Encrypt: message too large to be encrypted (actual %d",
" max allowed %d)", messageStr.size(),
rsaSize - kOaepMinPadding );
return "";
}
// https://tools.ietf.org/html/rfc2437#section-9.1.1.2
//
// result db
// |------------------------------------------------------------------------|
// |0| seed | pHash |000000000|1| M |
// |------------------------------------------------------------------------|
// | |<-mdLength->|<-mdLength->|<-psLen->| |<-------messageLength---------->|
// |<------------paddingLength------------>|
std::string ret;
ret.resize(rsaSize);
size_t messageLength = messageStr.length();
size_t paddingLength = rsaSize - messageLength;
size_t psLen = paddingLength - kOaepMinPadding;
const uint8_t *message = reinterpret_cast<const uint8_t*>(messageStr.data());
uint8_t *result = reinterpret_cast<uint8_t*>(&ret[0]);
uint8_t *seed = result + 1;
uint8_t *db = result + CC_SHA1_DIGEST_LENGTH + 1;
// Initialize db and message
CC_SHA1(NULL, 0, db); // Hash of empty string.
result[rsaSize - messageLength - 1] = 0x1;
memcpy(result + paddingLength, message, messageLength);
// Initialize seed
if (SecRandomCopyBytes(kSecRandomDefault, CC_SHA1_DIGEST_LENGTH, seed)) {
LOGE("RsaPublicKey::Encrypt: unable to get random data %d", errno);
return "";
}
// Create the first mask
std::vector<uint8_t> dbmask;
dbmask.resize(rsaSize - CC_SHA1_DIGEST_LENGTH - 1);
ApplyMGF1_SHA1(dbmask.data(), dbmask.size(), seed, CC_SHA1_DIGEST_LENGTH);
for (int i = 0; i < dbmask.size(); i++) {
db[i] ^= dbmask[i];
}
// Create the second mask
uint8_t seedmask[CC_SHA1_DIGEST_LENGTH];
ApplyMGF1_SHA1(seedmask, CC_SHA1_DIGEST_LENGTH, db, dbmask.size());
for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++) {
seed[i] ^= seedmask[i];
}
return ret;
}
bool PSSVerify(const uint8_t *message, size_t messageLength,
const uint8_t *encodedMessage, size_t encodedMessageLength) {
// https://tools.ietf.org/html/rfc3447#section-9.1.2
//
// M'
// |---------------------------------------------------|
// | 00 00 00 00 00 00 00 00 | mHash | salt |
// |---------------------------------------------------|
//
// H = hash(M')
// dbMask = MGF(H)
//
// db
// |------------------------|
// | 00 00 ... 00 01 | salt |
// |------------------------|
// |<----messageLength----->|
//
// maskedDb = db ^ dbMask
// encodedMessage
// |--------------------------------------------------|
// | maskedDb | H | bc |
// |--------------------------------------------------|
if (encodedMessage[encodedMessageLength - 1] != 0xbc) {
return false;
}
const uint8_t *maskedDb = encodedMessage;
size_t dbLength = encodedMessageLength - CC_SHA1_DIGEST_LENGTH - 1;
const uint8_t *H = maskedDb + dbLength;
// Decode db
std::vector<uint8_t> dbMask;
dbMask.resize(dbLength);
ApplyMGF1_SHA1(dbMask.data(), dbMask.size(), H, CC_SHA1_DIGEST_LENGTH);
for (int i = 0; i < dbLength; i++) {
dbMask[i] ^= maskedDb[i];
}
// Verify db
for (int i = 0; i < dbLength - kPssSaltLength - 1; i++) {
if (dbMask[i] != 0) {
return false;
}
}
if (dbMask[dbLength - kPssSaltLength - 1] != 0x01) {
return false;
}
uint8_t *salt = dbMask.data() + (dbLength - kPssSaltLength);
uint8_t mHash[CC_SHA1_DIGEST_LENGTH];
CC_SHA1(message, messageLength, mHash);
// Create our version of the message data (M')
std::vector<uint8_t> dataVec;
dataVec.resize(8 + CC_SHA1_DIGEST_LENGTH + kPssSaltLength);
uint8_t *data = dataVec.data();
memcpy(data + 8, mHash, CC_SHA1_DIGEST_LENGTH);
memcpy(data + 8 + CC_SHA1_DIGEST_LENGTH, salt, kPssSaltLength);
// Verify the hash of the message data.
uint8_t H2[CC_SHA1_DIGEST_LENGTH];
CC_SHA1(data, dataVec.size(), H2);
return !memcmp(H, H2, CC_SHA1_DIGEST_LENGTH);
}
} // namespace
namespace wvcdm {
AesCbcKey::AesCbcKey() {}
AesCbcKey::~AesCbcKey() {}
bool AesCbcKey::Init(const std::string& key) {
assert(key.size() == kCCBlockSizeAES128);
this->key_ = key;
return true;
}
bool AesCbcKey::Encrypt(const std::string& in, std::string* out,
std::string* iv) {
assert(!in.empty());
assert(iv != NULL);
assert(iv->size() == kCCBlockSizeAES128);
assert(out != NULL);
assert(!key_.empty());
std::string temp;
temp.resize(in.length() + kCCBlockSizeAES128);
size_t length;
CCCryptorStatus result = CCCrypt(kCCEncrypt, kCCAlgorithmAES128,
kCCOptionPKCS7Padding, key_.c_str(), key_.length(), iv->c_str(),
in.c_str(), in.length(), &temp[0], temp.size(), &length);
if (result != kCCSuccess) {
LOGE("AesCbcKey::Encrypt: Encryption failure: %d", result);
return false;
}
out->assign(temp, 0, length);
return true;
}
RsaPublicKey::RsaPublicKey() {}
RsaPublicKey::~RsaPublicKey() {}
bool RsaPublicKey::Init(const std::string& serialized_key) {
assert(!serialized_key.empty());
this->serialized_key_ = serialized_key;
return true;
}
bool RsaPublicKey::Encrypt(const std::string& clear_message,
std::string* encrypted_message) {
assert(!clear_message.empty());
assert(encrypted_message != NULL);
SecKeyRef key = ImportPublicKey(serialized_key_);
if (!key) {
return false;
}
size_t rsaSize = SecKeyGetBlockSize(key);
std::string paddedMessage = ApplyOAEPPadding(clear_message, rsaSize);
if (paddedMessage.empty()) {
return false;
}
size_t size = paddedMessage.length();
std::string buffer;
buffer.resize(size);
OSStatus status = SecKeyEncrypt(key, kSecPaddingNone,
reinterpret_cast<const uint8_t*>(paddedMessage.c_str()),
paddedMessage.length(),
reinterpret_cast<uint8_t*>(&buffer[0]), &size);
if (status != errSecSuccess) {
LOGE("RsaPublicKey::Encrypt: Unable to encrypt data %d", status);
return false;
}
encrypted_message->assign(buffer, 0, size);
return true;
}
bool RsaPublicKey::VerifySignature(const std::string& message,
const std::string& signature) {
assert(!message.empty());
assert(!signature.empty());
SecKeyRef key = ImportPublicKey(serialized_key_);
if (!key) {
return false;
}
// "decrypt" the signature
std::vector<uint8_t> buffer;
buffer.resize(signature.length());
size_t size = buffer.size();
OSStatus status = SecKeyEncrypt(key, kSecPaddingNone,
reinterpret_cast<const uint8_t*>(signature.c_str()),
signature.length(),
buffer.data(), &size);
if (status != errSecSuccess) {
LOGE("RsaPublicKey::VerifySignature: Unable to decrypt signature %d",
status);
return false;
}
// Verify the signature
if (!PSSVerify(reinterpret_cast<const uint8_t*>(message.c_str()),
message.length(),
buffer.data(), buffer.size())) {
LOGE("RsaPublicKey::VerifySignature: Unable to verify signature %d",
status);
return false;
}
return true;
}
} // namespace wvcdm

View File

@@ -230,7 +230,6 @@ TEST_F(CdmSessionTest, ReInitFail) {
}
TEST_F(CdmSessionTest, InitFailCryptoError) {
CdmSecurityLevel level = kSecurityLevelL1;
EXPECT_CALL(*crypto_session_, Open(Eq(kLevelDefault)))
.WillOnce(Return(UNKNOWN_ERROR));

View File

@@ -146,7 +146,7 @@ ConfigTestEnv::ConfigTestEnv(LicenseServerId server_id, bool streaming,
if (!streaming) {
key_id_ = license_servers[server_id].offline_key_id;
if (wvcdm::kGooglePlayServer == server_id) {
if (kGooglePlayServer == server_id) {
if (renew) {
client_auth_.append(kGpClientOfflineRenewalQueryParameters);
} else if (release) {

View File

@@ -1469,7 +1469,6 @@ class DeviceFilesTest : public ::testing::Test {
CdmAppParameterMap app_parameters;
size_t start_pos = 0;
size_t len = str.length();
bool more = true;
while (start_pos < len) {
size_t name_end_pos = str.find(' ', start_pos);
if (name_end_pos == std::string::npos) return app_parameters;
@@ -1841,7 +1840,6 @@ TEST_F(DeviceFilesTest, RetrieveLicenses) {
DeviceFiles device_files;
EXPECT_TRUE(device_files.Init(kSecurityLevelL1));
device_files.SetTestFile(&file);
DeviceFiles::LicenseState license_state;
CdmInitData pssh_data;
CdmKeyMessage key_request;
CdmKeyResponse key_response;

View File

@@ -47,6 +47,7 @@ SSL_CTX* InitSslContext() {
return ctx;
}
#if 0
// unused, may be useful for debugging SSL-related issues.
void ShowServerCertificate(const SSL* ssl) {
// gets the server certificate
@@ -64,6 +65,7 @@ void ShowServerCertificate(const SSL* ssl) {
LOGE("Failed to get server certificate");
}
}
#endif
// Wait for a socket to be ready for reading or writing.
// Establishing a connection counts as "ready for write".

View File

@@ -16,7 +16,6 @@ namespace wvcdm {
namespace {
const uint32_t kAesBlockSize = 16;
const std::string kAesKey = a2bs_hex("000102030405060708090a0b0c0d0e0f");
const std::string kAesIv = a2bs_hex("000102030405060708090a0b0c0d0e0f");
const std::string kCencInitDataHdr = a2bs_hex(

View File

@@ -338,6 +338,8 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) {
case EMPTY_LICENSE_REQUEST: *os << "EMPTY_LICENSE_REQUEST";
break;
case DUPLICATE_SESSION_ID_SPECIFIED: *os << "DUPLICATE_SESSION_ID_SPECIFIED";
case LICENSE_RENEWAL_PROHIBITED: *os << "LICENSE_RENEWAL_PROHIBITED";
break;
default:
*os << "Unknown CdmResponseType";
break;

View File

@@ -1,53 +0,0 @@
#!/bin/bash
ROOT=$(dirname "$0")
if [ ! -e "$ROOT"/third_party ]; then
# Potentially run from a different folder before packaging.
ROOT=$(dirname "$ROOT")
fi
if [ ! -e "$ROOT"/third_party ]; then
echo "Unable to find third_party sources!" 1>&2
exit 1
fi
ROOT=$(realpath "$ROOT")
TMP=$(mktemp -d)
trap "rm -rf $TMP" EXIT
set -x
set -e
cd $TMP
# Check out and install gyp locally
svn checkout https://gyp.googlecode.com/svn/trunk/ gyp -r1846
rm -rf "$ROOT"/third_party/gyp
mv gyp/pylib/gyp "$ROOT"/third_party/
# Check out and install stringencoders locally
wget https://stringencoders.googlecode.com/files/stringencoders-v3.10.3.tar.gz
tar xzf stringencoders-v3.10.3.tar.gz
(
cd stringencoders-v3.10.3
./configure --with-b64wchars='-_='
make modp_b64w_data.h
make src/modp_b64w.c
mkdir -p "$ROOT"/third_party/stringencoders/src
cp src/modp_b64w.c "$ROOT"/third_party/stringencoders/src/modp_b64w.cpp
cp src/modp_b64w.h modp_b64w_data.h "$ROOT"/third_party/stringencoders/src/
>"$ROOT"/third_party/stringencoders/src/config.h
)
# Check out and install gmock locally
wget https://googlemock.googlecode.com/files/gmock-1.7.0.zip
unzip gmock-1.7.0.zip
rm -rf "$ROOT"/third_party/gmock
mv gmock-1.7.0 "$ROOT"/third_party/gmock
# Check out and install protobuf locally
wget https://protobuf.googlecode.com/files/protobuf-2.5.0.tar.gz
tar xzf protobuf-2.5.0.tar.gz
(cd protobuf-2.5.0 && ./configure)
rm -rf "$ROOT"/third_party/protobuf
mv protobuf-2.5.0 "$ROOT"/third_party/protobuf

View File

@@ -243,7 +243,7 @@ typedef enum OEMCrypto_HDCP_Capability {
HDCP_V1 = 1, // HDCP version 1.0
HDCP_V2 = 2, // HDCP version 2.0
HDCP_V2_1 = 3, // HDCP version 2.1
HDCP_V2_2 = 4, // HDCP version 2.2
HDCP_V2_2 = 4, // HDCP version 2.2 Type 1.
HDCP_NO_DIGITAL_OUTPUT = 0xff // No digital output.
} OEMCrypto_HDCP_Capability;

View File

@@ -72,9 +72,9 @@ typedef struct {
// Note: The API does not specify a maximum key id length. We specify a
// maximum just for these tests, so that we have a fixed message size.
const size_t kTestKeyIdMaxLength = 48;
// Most content will use a key id that is 12 bytes long.
const int kDefaultKeyIdLength = 12;
const size_t kTestKeyIdMaxLength = 16;
// Most content will use a key id that is 16 bytes long.
const int kDefaultKeyIdLength = 16;
typedef struct {
uint8_t key_id[kTestKeyIdMaxLength];
size_t key_id_length;
@@ -101,11 +101,6 @@ struct RSAPrivateKeyMessage {
uint32_t nonce;
};
struct PaddedPSTReport {
OEMCrypto_PST_Report report;
uint8_t padding[256];
};
// These are test keyboxes. They will not be accepted by production systems.
// By using known keyboxes for these tests, the results for a given set of
// inputs to a test are predictable and can be compared to the actual results.
@@ -364,239 +359,6 @@ static const uint8_t kTestRSAPKCS8PrivateKeyInfo2_2048[] = {
0x72, 0x2c, 0xf7, 0xc1, 0x22, 0x36, 0xd9, 0x18,
0x56, 0xfe, 0x39, 0x28, 0x33, 0xe0, 0xdb, 0x03 };
// A 2048 bit RSA Public key
// Used to verify the functions that manipulate RSA keys.
static const uint8_t kTestRSAPublicKey2_2048[] = {
0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01,
0x00, 0xa7, 0x00, 0x36, 0x60, 0x65, 0xdc, 0xbd,
0x54, 0x5a, 0x2a, 0x40, 0xb4, 0xe1, 0x15, 0x94,
0x58, 0x11, 0x4f, 0x94, 0x58, 0xdd, 0xde, 0xa7,
0x1f, 0x3c, 0x2c, 0xe0, 0x88, 0x09, 0x29, 0x61,
0x57, 0x67, 0x5e, 0x56, 0x7e, 0xee, 0x27, 0x8f,
0x59, 0x34, 0x9a, 0x2a, 0xaa, 0x9d, 0xb4, 0x4e,
0xfa, 0xa7, 0x6a, 0xd4, 0xc9, 0x7a, 0x53, 0xc1,
0x4e, 0x9f, 0xe3, 0x34, 0xf7, 0x3d, 0xb7, 0xc9,
0x10, 0x47, 0x4f, 0x28, 0xda, 0x3f, 0xce, 0x31,
0x7b, 0xfd, 0x06, 0x10, 0xeb, 0xf7, 0xbe, 0x92,
0xf9, 0xaf, 0xfb, 0x3e, 0x68, 0xda, 0xee, 0x1a,
0x64, 0x4c, 0xf3, 0x29, 0xf2, 0x73, 0x9e, 0x39,
0xd8, 0xf6, 0x6f, 0xd8, 0xb2, 0x80, 0x82, 0x71,
0x8e, 0xb5, 0xa4, 0xf2, 0xc2, 0x3e, 0xcd, 0x0a,
0xca, 0xb6, 0x04, 0xcd, 0x9a, 0x13, 0x8b, 0x54,
0x73, 0x54, 0x25, 0x54, 0x8c, 0xbe, 0x98, 0x7a,
0x67, 0xad, 0xda, 0xb3, 0x4e, 0xb3, 0xfa, 0x82,
0xa8, 0x4a, 0x67, 0x98, 0x56, 0x57, 0x54, 0x71,
0xcd, 0x12, 0x7f, 0xed, 0xa3, 0x01, 0xc0, 0x6a,
0x8b, 0x24, 0x03, 0x96, 0x88, 0xbe, 0x97, 0x66,
0x2a, 0xbc, 0x53, 0xc9, 0x83, 0x06, 0x51, 0x5a,
0x88, 0x65, 0x13, 0x18, 0xe4, 0x3a, 0xed, 0x6b,
0xf1, 0x61, 0x5b, 0x4c, 0xc8, 0x1e, 0xf4, 0xc2,
0xae, 0x08, 0x5e, 0x2d, 0x5f, 0xf8, 0x12, 0x7f,
0xa2, 0xfc, 0xbb, 0x21, 0x18, 0x30, 0xda, 0xfe,
0x40, 0xfb, 0x01, 0xca, 0x2e, 0x37, 0x0e, 0xce,
0xdd, 0x76, 0x87, 0x82, 0x46, 0x0b, 0x3a, 0x77,
0x8f, 0xc0, 0x72, 0x07, 0x2c, 0x7f, 0x9d, 0x1e,
0x86, 0x5b, 0xed, 0x27, 0x29, 0xdf, 0x03, 0x97,
0x62, 0xef, 0x44, 0xd3, 0x5b, 0x3d, 0xdb, 0x9c,
0x5e, 0x1b, 0x7b, 0x39, 0xb4, 0x0b, 0x6d, 0x04,
0x6b, 0xbb, 0xbb, 0x2c, 0x5f, 0xcf, 0xb3, 0x7a,
0x05, 0x02, 0x03, 0x01, 0x00, 0x01 };
// A second 2048 bit RSA key in PKCS#8 PrivateKeyInfo format
// Used to verify the functions that manipulate RSA keys.
static const uint8_t kTestRSAPKCS8PrivateKeyInfo3_2048[] = {
0x30, 0x82, 0x04, 0xbe, 0x02, 0x01, 0x00, 0x30,
0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82,
0x04, 0xa8, 0x30, 0x82, 0x04, 0xa4, 0x02, 0x01,
0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa5, 0xd0,
0xd7, 0x3e, 0x0e, 0x2d, 0xfb, 0x43, 0x51, 0x99,
0xea, 0x40, 0x1e, 0x2d, 0x89, 0xe4, 0xa2, 0x3e,
0xfc, 0x51, 0x3d, 0x0e, 0x83, 0xa7, 0xe0, 0xa5,
0x41, 0x04, 0x1e, 0x14, 0xc5, 0xa7, 0x5c, 0x61,
0x36, 0x44, 0xb3, 0x08, 0x05, 0x5b, 0x14, 0xde,
0x01, 0x0c, 0x32, 0x3c, 0x9a, 0x91, 0x00, 0x50,
0xa8, 0x1d, 0xcc, 0x9f, 0x8f, 0x35, 0xb7, 0xc2,
0x75, 0x08, 0x32, 0x8b, 0x10, 0x3a, 0x86, 0xf9,
0xd7, 0x78, 0xa3, 0x9d, 0x74, 0x10, 0xc6, 0x24,
0xb1, 0x7f, 0xa5, 0xbf, 0x5f, 0xc2, 0xd7, 0x15,
0xa3, 0x1d, 0xe0, 0x15, 0x6b, 0x1b, 0x0e, 0x38,
0xba, 0x34, 0xbc, 0x95, 0x47, 0x94, 0x40, 0x70,
0xac, 0x99, 0x1f, 0x0b, 0x8e, 0x56, 0x93, 0x36,
0x2b, 0x6d, 0x04, 0xe7, 0x95, 0x1a, 0x37, 0xda,
0x16, 0x57, 0x99, 0xee, 0x03, 0x68, 0x16, 0x31,
0xaa, 0xc3, 0xb7, 0x92, 0x75, 0x53, 0xfc, 0xf6,
0x20, 0x55, 0x44, 0xf8, 0xd4, 0x8d, 0x78, 0x15,
0xc7, 0x1a, 0xb6, 0xde, 0x6c, 0xe8, 0x49, 0x5d,
0xaf, 0xa8, 0x4e, 0x6f, 0x7c, 0xe2, 0x6a, 0x4c,
0xd5, 0xe7, 0x8c, 0x8f, 0x0b, 0x5d, 0x3a, 0x09,
0xd6, 0xb3, 0x44, 0xab, 0xe0, 0x35, 0x52, 0x7c,
0x66, 0x85, 0xa4, 0x40, 0xd7, 0x20, 0xec, 0x24,
0x05, 0x06, 0xd9, 0x84, 0x51, 0x5a, 0xd2, 0x38,
0xd5, 0x1d, 0xea, 0x70, 0x2a, 0x21, 0xe6, 0x82,
0xfd, 0xa4, 0x46, 0x1c, 0x4f, 0x59, 0x6e, 0x29,
0x3d, 0xae, 0xb8, 0x8e, 0xee, 0x77, 0x1f, 0x15,
0x33, 0xcf, 0x94, 0x1d, 0x87, 0x3c, 0x37, 0xc5,
0x89, 0xe8, 0x7d, 0x85, 0xb3, 0xbc, 0xe8, 0x62,
0x6a, 0x84, 0x7f, 0xfe, 0x9a, 0x85, 0x3f, 0x39,
0xe8, 0xaa, 0x16, 0xa6, 0x8f, 0x87, 0x7f, 0xcb,
0xc1, 0xd6, 0xf2, 0xec, 0x2b, 0xa7, 0xdd, 0x49,
0x98, 0x7b, 0x6f, 0xdd, 0x69, 0x6d, 0x02, 0x03,
0x01, 0x00, 0x01, 0x02, 0x82, 0x01, 0x00, 0x43,
0x8f, 0x19, 0x83, 0xb1, 0x27, 0x4e, 0xee, 0x98,
0xba, 0xcb, 0x54, 0xa0, 0x77, 0x11, 0x6d, 0xd4,
0x25, 0x31, 0x8c, 0xb0, 0x01, 0xcf, 0xe6, 0x80,
0x83, 0x14, 0x40, 0x67, 0x39, 0x33, 0x67, 0x03,
0x1e, 0xa0, 0x8b, 0xd1, 0x1d, 0xfd, 0x80, 0xa4,
0xb9, 0xe7, 0x57, 0x5e, 0xc8, 0x8e, 0x79, 0x71,
0xd5, 0x6b, 0x09, 0xe9, 0x2b, 0x41, 0xa0, 0x33,
0x64, 0xc9, 0x66, 0x33, 0xa1, 0xb1, 0x55, 0x07,
0x55, 0x98, 0x53, 0x10, 0xe6, 0xc0, 0x39, 0x6d,
0x61, 0xd9, 0xe8, 0x16, 0x52, 0x28, 0xe4, 0x2b,
0xda, 0x27, 0x01, 0xaf, 0x21, 0x4a, 0xe8, 0x55,
0x1d, 0x0b, 0xd1, 0x1c, 0xdc, 0xfd, 0xb3, 0x0b,
0xa6, 0x5c, 0xcc, 0x6e, 0x77, 0xb8, 0xe0, 0xd1,
0x4e, 0x0a, 0xd7, 0x7a, 0x5e, 0x18, 0xc3, 0xfb,
0xe9, 0xa1, 0x9c, 0xc3, 0x9c, 0xd4, 0x4a, 0x7e,
0x70, 0x72, 0x11, 0x18, 0x24, 0x56, 0x24, 0xdf,
0xf8, 0xba, 0xac, 0x5b, 0x54, 0xd3, 0xc4, 0x65,
0x69, 0xc8, 0x79, 0x94, 0x16, 0x88, 0x9a, 0x68,
0x1c, 0xbc, 0xd4, 0xca, 0xec, 0x5e, 0x07, 0x4a,
0xc9, 0x54, 0x7a, 0x4b, 0xdb, 0x19, 0x88, 0xf6,
0xbe, 0x50, 0x9d, 0x9e, 0x9d, 0x88, 0x5b, 0x4a,
0x23, 0x86, 0x2b, 0xa9, 0xa6, 0x6c, 0x70, 0x7d,
0xe1, 0x11, 0xba, 0xbf, 0x03, 0x2e, 0xf1, 0x46,
0x7e, 0x1b, 0xed, 0x06, 0x11, 0x57, 0xad, 0x4a,
0xcb, 0xe5, 0xb1, 0x11, 0x05, 0x0a, 0x30, 0xb1,
0x73, 0x79, 0xcd, 0x7a, 0x04, 0xcc, 0x70, 0xe9,
0x95, 0xe4, 0x27, 0xc2, 0xd5, 0x2d, 0x92, 0x44,
0xdf, 0xb4, 0x94, 0xa8, 0x73, 0xa1, 0x4a, 0xc3,
0xcc, 0xc4, 0x0e, 0x8d, 0xa1, 0x6a, 0xc2, 0xd8,
0x03, 0x7f, 0xfa, 0xa7, 0x76, 0x0d, 0xad, 0x87,
0x88, 0xa0, 0x77, 0xaf, 0x3b, 0x23, 0xd1, 0x66,
0x0b, 0x31, 0x2b, 0xaf, 0xef, 0xd5, 0x41, 0x02,
0x81, 0x81, 0x00, 0xdb, 0xc1, 0xe7, 0xdd, 0xba,
0x3c, 0x1f, 0x9c, 0x64, 0xca, 0xa0, 0x63, 0xdb,
0xd2, 0x47, 0x5c, 0x6e, 0x8a, 0xa3, 0x16, 0xd5,
0xda, 0xc2, 0x25, 0x64, 0x0a, 0x02, 0xbc, 0x7d,
0x7f, 0x50, 0xab, 0xe0, 0x66, 0x03, 0x53, 0x7d,
0x77, 0x6d, 0x6c, 0x61, 0x58, 0x09, 0x73, 0xcd,
0x18, 0xe9, 0x53, 0x0b, 0x5c, 0xa2, 0x71, 0x14,
0x02, 0xfd, 0x55, 0xda, 0xe9, 0x77, 0x24, 0x7c,
0x2a, 0x4e, 0xb9, 0xd9, 0x5d, 0x58, 0xf6, 0x26,
0xd0, 0xd8, 0x3d, 0xcf, 0x8c, 0x89, 0x65, 0x6c,
0x35, 0x19, 0xb6, 0x63, 0xff, 0xa0, 0x71, 0x49,
0xcd, 0x6d, 0x5b, 0x3d, 0x8f, 0xea, 0x6f, 0xa9,
0xba, 0x43, 0xe5, 0xdd, 0x39, 0x3a, 0x78, 0x8f,
0x07, 0xb8, 0xab, 0x58, 0x07, 0xb7, 0xd2, 0xf8,
0x07, 0x02, 0x9b, 0x79, 0x26, 0x32, 0x22, 0x38,
0x91, 0x01, 0x90, 0x81, 0x29, 0x94, 0xad, 0x77,
0xeb, 0x86, 0xb9, 0x02, 0x81, 0x81, 0x00, 0xc1,
0x29, 0x88, 0xbd, 0x96, 0x31, 0x33, 0x7b, 0x77,
0x5d, 0x32, 0x12, 0x5e, 0xdf, 0x28, 0x0c, 0x96,
0x0d, 0xa8, 0x22, 0xdf, 0xd3, 0x35, 0xd7, 0xb0,
0x41, 0xcb, 0xe7, 0x94, 0x8a, 0xa4, 0xed, 0xd2,
0xfb, 0xd2, 0xf3, 0xf2, 0x95, 0xff, 0xd8, 0x33,
0x3f, 0x8c, 0xd7, 0x65, 0xe4, 0x0c, 0xcc, 0xfe,
0x32, 0x66, 0xfa, 0x50, 0xe2, 0xcf, 0xf0, 0xbe,
0x05, 0xb1, 0xbc, 0xbe, 0x44, 0x09, 0xb4, 0xfe,
0x95, 0x06, 0x18, 0xd7, 0x59, 0xc6, 0xef, 0x2d,
0x22, 0xa0, 0x73, 0x5e, 0x77, 0xdf, 0x8d, 0x09,
0x2c, 0xb8, 0xcc, 0xeb, 0x10, 0x4d, 0xa7, 0xd0,
0x4b, 0x46, 0xba, 0x7d, 0x8b, 0x6a, 0x55, 0x47,
0x55, 0xd3, 0xd7, 0xb1, 0x88, 0xfd, 0x27, 0x3e,
0xf9, 0x5b, 0x7b, 0xae, 0x6d, 0x08, 0x9f, 0x0c,
0x2a, 0xe1, 0xdd, 0xb9, 0xe3, 0x55, 0x13, 0x55,
0xa3, 0x6d, 0x06, 0xbb, 0xe0, 0x1e, 0x55, 0x02,
0x81, 0x80, 0x61, 0x73, 0x3d, 0x64, 0xff, 0xdf,
0x05, 0x8d, 0x8e, 0xcc, 0xa4, 0x0f, 0x64, 0x3d,
0x7d, 0x53, 0xa9, 0xd9, 0x64, 0xb5, 0x0d, 0xa4,
0x72, 0x8f, 0xae, 0x2b, 0x1a, 0x47, 0x87, 0xc7,
0x5b, 0x78, 0xbc, 0x8b, 0xc0, 0x51, 0xd7, 0xc3,
0x8c, 0x0c, 0x91, 0xa6, 0x3e, 0x9a, 0xd1, 0x8a,
0x88, 0x7d, 0x40, 0xfe, 0x95, 0x32, 0x5b, 0xd3,
0x6f, 0x90, 0x11, 0x01, 0x92, 0xc9, 0xe5, 0x1d,
0xc5, 0xc7, 0x78, 0x72, 0x82, 0xae, 0xb5, 0x4b,
0xcb, 0x78, 0xad, 0x7e, 0xfe, 0xb6, 0xb1, 0x23,
0x63, 0x01, 0x94, 0x9a, 0x99, 0x05, 0x63, 0xda,
0xea, 0xf1, 0x98, 0xfd, 0x26, 0xd2, 0xd9, 0x8b,
0x35, 0xec, 0xcb, 0x0b, 0x43, 0xb8, 0x8e, 0x84,
0xb8, 0x09, 0x93, 0x81, 0xe8, 0xac, 0x6f, 0x3c,
0x7c, 0x95, 0x81, 0x45, 0xc4, 0xd9, 0x94, 0x08,
0x09, 0x8f, 0x91, 0x17, 0x65, 0x4c, 0xff, 0x6e,
0xbc, 0x51, 0x02, 0x81, 0x81, 0x00, 0xc1, 0x0d,
0x9d, 0xd8, 0xbd, 0xaf, 0x56, 0xe0, 0xe3, 0x1f,
0x85, 0xd7, 0xce, 0x72, 0x02, 0x38, 0xf2, 0x0f,
0x9c, 0x27, 0x9e, 0xc4, 0x1d, 0x60, 0x00, 0x8d,
0x02, 0x19, 0xe5, 0xdf, 0xdb, 0x8e, 0xc5, 0xfb,
0x61, 0x8e, 0xe6, 0xb8, 0xfc, 0x07, 0x3c, 0xd1,
0x1b, 0x16, 0x7c, 0x83, 0x3c, 0x37, 0xf5, 0x26,
0xb2, 0xbd, 0x22, 0xf2, 0x4d, 0x19, 0x33, 0x11,
0xc5, 0xdd, 0xf9, 0xdb, 0x4e, 0x48, 0x52, 0xd8,
0xe6, 0x4b, 0x15, 0x90, 0x68, 0xbe, 0xca, 0xc1,
0x7c, 0xd3, 0x51, 0x6b, 0x45, 0x46, 0x54, 0x11,
0x1a, 0x71, 0xd3, 0xcd, 0x6b, 0x8f, 0x79, 0x22,
0x83, 0x02, 0x08, 0x4f, 0xba, 0x6a, 0x98, 0xed,
0x32, 0xd8, 0xb4, 0x5b, 0x51, 0x88, 0x53, 0xec,
0x2c, 0x7e, 0xa4, 0x89, 0xdc, 0xbf, 0xf9, 0x0d,
0x32, 0xc8, 0xc3, 0xec, 0x6d, 0x2e, 0xf1, 0xbc,
0x70, 0x4e, 0xf6, 0x9e, 0xbc, 0x31, 0x02, 0x81,
0x81, 0x00, 0xd3, 0x35, 0x1b, 0x19, 0x75, 0x3f,
0x61, 0xf2, 0x55, 0x03, 0xce, 0x25, 0xa9, 0xdf,
0x0c, 0x0a, 0x3b, 0x47, 0x42, 0xdc, 0x38, 0x4b,
0x13, 0x4d, 0x1f, 0x86, 0x58, 0x4f, 0xd8, 0xee,
0xfa, 0x76, 0x15, 0xfb, 0x6e, 0x55, 0x31, 0xf2,
0xd2, 0x62, 0x32, 0xa5, 0xc4, 0x23, 0x5e, 0x08,
0xa9, 0x83, 0x07, 0xac, 0x8c, 0xa3, 0x7e, 0x18,
0xc0, 0x1c, 0x57, 0x63, 0x8d, 0x05, 0x17, 0x47,
0x1b, 0xd3, 0x74, 0x73, 0x20, 0x04, 0xfb, 0xc8,
0x1a, 0x43, 0x04, 0x36, 0xc8, 0x19, 0xbe, 0xdc,
0xa6, 0xe5, 0x0f, 0x25, 0x62, 0x24, 0x96, 0x92,
0xb6, 0xb3, 0x97, 0xad, 0x57, 0x9a, 0x90, 0x37,
0x4e, 0x31, 0x44, 0x74, 0xfa, 0x7c, 0xb4, 0xea,
0xfc, 0x15, 0xa7, 0xb0, 0x51, 0xcc, 0xee, 0x1e,
0xed, 0x5b, 0x98, 0x18, 0x0e, 0x65, 0xb6, 0x4b,
0x69, 0x0b, 0x21, 0xdc, 0x86, 0x17, 0x6e, 0xc8,
0xee, 0x24 };
// A second 2048 bit RSA Public key
// Used to verify the functions that manipulate RSA keys.
static const uint8_t kTestRSAPublicKey3_2048[] = {
0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01,
0x00, 0xa5, 0xd0, 0xd7, 0x3e, 0x0e, 0x2d, 0xfb,
0x43, 0x51, 0x99, 0xea, 0x40, 0x1e, 0x2d, 0x89,
0xe4, 0xa2, 0x3e, 0xfc, 0x51, 0x3d, 0x0e, 0x83,
0xa7, 0xe0, 0xa5, 0x41, 0x04, 0x1e, 0x14, 0xc5,
0xa7, 0x5c, 0x61, 0x36, 0x44, 0xb3, 0x08, 0x05,
0x5b, 0x14, 0xde, 0x01, 0x0c, 0x32, 0x3c, 0x9a,
0x91, 0x00, 0x50, 0xa8, 0x1d, 0xcc, 0x9f, 0x8f,
0x35, 0xb7, 0xc2, 0x75, 0x08, 0x32, 0x8b, 0x10,
0x3a, 0x86, 0xf9, 0xd7, 0x78, 0xa3, 0x9d, 0x74,
0x10, 0xc6, 0x24, 0xb1, 0x7f, 0xa5, 0xbf, 0x5f,
0xc2, 0xd7, 0x15, 0xa3, 0x1d, 0xe0, 0x15, 0x6b,
0x1b, 0x0e, 0x38, 0xba, 0x34, 0xbc, 0x95, 0x47,
0x94, 0x40, 0x70, 0xac, 0x99, 0x1f, 0x0b, 0x8e,
0x56, 0x93, 0x36, 0x2b, 0x6d, 0x04, 0xe7, 0x95,
0x1a, 0x37, 0xda, 0x16, 0x57, 0x99, 0xee, 0x03,
0x68, 0x16, 0x31, 0xaa, 0xc3, 0xb7, 0x92, 0x75,
0x53, 0xfc, 0xf6, 0x20, 0x55, 0x44, 0xf8, 0xd4,
0x8d, 0x78, 0x15, 0xc7, 0x1a, 0xb6, 0xde, 0x6c,
0xe8, 0x49, 0x5d, 0xaf, 0xa8, 0x4e, 0x6f, 0x7c,
0xe2, 0x6a, 0x4c, 0xd5, 0xe7, 0x8c, 0x8f, 0x0b,
0x5d, 0x3a, 0x09, 0xd6, 0xb3, 0x44, 0xab, 0xe0,
0x35, 0x52, 0x7c, 0x66, 0x85, 0xa4, 0x40, 0xd7,
0x20, 0xec, 0x24, 0x05, 0x06, 0xd9, 0x84, 0x51,
0x5a, 0xd2, 0x38, 0xd5, 0x1d, 0xea, 0x70, 0x2a,
0x21, 0xe6, 0x82, 0xfd, 0xa4, 0x46, 0x1c, 0x4f,
0x59, 0x6e, 0x29, 0x3d, 0xae, 0xb8, 0x8e, 0xee,
0x77, 0x1f, 0x15, 0x33, 0xcf, 0x94, 0x1d, 0x87,
0x3c, 0x37, 0xc5, 0x89, 0xe8, 0x7d, 0x85, 0xb3,
0xbc, 0xe8, 0x62, 0x6a, 0x84, 0x7f, 0xfe, 0x9a,
0x85, 0x3f, 0x39, 0xe8, 0xaa, 0x16, 0xa6, 0x8f,
0x87, 0x7f, 0xcb, 0xc1, 0xd6, 0xf2, 0xec, 0x2b,
0xa7, 0xdd, 0x49, 0x98, 0x7b, 0x6f, 0xdd, 0x69,
0x6d, 0x02, 0x03, 0x01, 0x00, 0x01 };
DeviceFeatures global_features;
void DeviceFeatures::Initialize(bool is_cast_receiver, bool force_load_test_keybox) {
@@ -1364,7 +1126,8 @@ class Session {
int status = RSA_public_encrypt(session_key.size(), &session_key[0],
&(enc_session_key->front()), public_rsa_,
RSA_PKCS1_OAEP_PADDING);
if (static_cast<unsigned>(status) != RSA_size(public_rsa_)) {
int size = static_cast<int>(RSA_size(public_rsa_));
if (status != size) {
cout << "GenerateRSASessionKey error encrypting session key. ";
dump_openssl_error();
return false;
@@ -1403,16 +1166,17 @@ class Session {
size_t length = 0;
OEMCryptoResult sts = OEMCrypto_ReportUsage(
session_id(), reinterpret_cast<const uint8_t*>(pst.c_str()),
pst.length(), &pst_report_.report, &length);
pst.length(), pst_report(), &length);
if (expect_success) {
ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts);
}
if (sts == OEMCrypto_ERROR_SHORT_BUFFER) {
ASSERT_GE(sizeof(PaddedPSTReport), length);
ASSERT_LE(sizeof(OEMCrypto_PST_Report), length);
pst_report_buffer_.resize(length);
}
sts = OEMCrypto_ReportUsage(session_id(),
reinterpret_cast<const uint8_t*>(pst.c_str()),
pst.length(), &pst_report_.report, &length);
pst.length(), pst_report(), &length);
if (!expect_success) {
ASSERT_NE(OEMCrypto_SUCCESS, sts);
return;
@@ -1421,16 +1185,19 @@ class Session {
vector<uint8_t> computed_signature(SHA_DIGEST_LENGTH);
unsigned int sig_len = SHA_DIGEST_LENGTH;
HMAC(EVP_sha1(), &mac_key_client_[0], mac_key_client_.size(),
reinterpret_cast<uint8_t*>(&pst_report_.report) + SHA_DIGEST_LENGTH,
reinterpret_cast<uint8_t*>(pst_report()) + SHA_DIGEST_LENGTH,
length - SHA_DIGEST_LENGTH, &computed_signature[0], &sig_len);
EXPECT_EQ(0, memcmp(&computed_signature[0], pst_report_.report.signature,
EXPECT_EQ(0, memcmp(&computed_signature[0], pst_report()->signature,
SHA_DIGEST_LENGTH));
EXPECT_GE(kInactive, pst_report_.report.status);
EXPECT_GE(kHardwareSecureClock, pst_report_.report.clock_security_level);
EXPECT_EQ(pst.length(), pst_report_.report.pst_length);
EXPECT_EQ(0, memcmp(pst.c_str(), pst_report_.report.pst, pst.length()));
EXPECT_GE(kInactive, pst_report()->status);
EXPECT_GE(kHardwareSecureClock, pst_report()->clock_security_level);
EXPECT_EQ(pst.length(), pst_report()->pst_length);
EXPECT_EQ(0, memcmp(pst.c_str(), pst_report()->pst, pst.length()));
}
OEMCrypto_PST_Report* pst_report() {
return reinterpret_cast<OEMCrypto_PST_Report*>(&pst_report_buffer_[0]);
}
OEMCrypto_PST_Report* pst_report() { return &pst_report_.report; }
void DeleteEntry(const std::string& pst) {
uint8_t* pst_ptr = encrypted_license_.pst;
@@ -1465,7 +1232,7 @@ class Session {
vector<uint8_t> enc_key_;
uint32_t nonce_;
RSA* public_rsa_;
PaddedPSTReport pst_report_;
vector<uint8_t> pst_report_buffer_;
MessageData license_;
MessageData encrypted_license_;
OEMCrypto_KeyObject key_array_[kNumKeys];
@@ -2445,7 +2212,6 @@ INSTANTIATE_TEST_CASE_P(TestRefreshEachKeys, SessionTestRefreshKeyTest,
// Decrypt Tests
//
TEST_F(OEMCryptoSessionTests, Decrypt) {
OEMCryptoResult sts;
Session s;
s.open();
@@ -2526,7 +2292,6 @@ TEST_F(OEMCryptoSessionTests, DecryptPerformance) {
}
TEST_F(OEMCryptoSessionTests, DecryptZeroDuration) {
OEMCryptoResult sts;
Session s;
s.open();
@@ -2592,9 +2357,9 @@ class OEMCryptoSessionTestsDecryptEdgeCases : public OEMCryptoSessionTests {
s.FillSimpleMessage(kDuration, 0, 0);
s.EncryptAndSign();
s.LoadTestKeys();
// Select the key (from FillSimpleMessage)
vector<uint8_t> keyId = wvcdm::a2b_hex("000000000000000000000000");
sts = OEMCrypto_SelectKey(s.session_id(), &keyId[0], keyId.size());
sts = OEMCrypto_SelectKey(s.session_id(),
s.license().keys[0].key_id,
s.license().keys[0].key_id_length);
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
// We decrypt three subsamples. each with a block offset.
@@ -2797,7 +2562,6 @@ TEST_F(OEMCryptoSessionTests, DecryptUnencryptedNoKey) {
}
TEST_F(OEMCryptoSessionTests, DecryptSecureToClear) {
OEMCryptoResult sts;
Session s;
s.open();
s.GenerateTestSessionKeys();
@@ -2809,7 +2573,6 @@ TEST_F(OEMCryptoSessionTests, DecryptSecureToClear) {
}
TEST_F(OEMCryptoSessionTests, KeyDuration) {
OEMCryptoResult sts;
Session s;
s.open();
s.GenerateTestSessionKeys();
@@ -2846,7 +2609,8 @@ class OEMCryptoLoadsCertificate : public OEMCryptoSessionTestKeyboxTest {
s.RewrapRSAKey(encrypted, signature, wrapped_key, force);
// Verify that the clear key is not contained in the wrapped key.
// It should be encrypted.
ASSERT_EQ(NULL, find(*wrapped_key, encoded_rsa_key_)); }
ASSERT_EQ(NULL, find(*wrapped_key, encoded_rsa_key_));
}
std::vector<uint8_t> encoded_rsa_key_;
};
@@ -4622,7 +4386,6 @@ TEST_F(GenericCryptoTest, KeyDurationDecrypt) {
}
TEST_F(GenericCryptoTest, KeyDurationSign) {
OEMCryptoResult sts;
EncryptAndLoadKeys();
unsigned int key_index = 2;
@@ -4655,7 +4418,6 @@ TEST_F(GenericCryptoTest, KeyDurationSign) {
}
TEST_F(GenericCryptoTest, KeyDurationVerify) {
OEMCryptoResult sts;
EncryptAndLoadKeys();
unsigned int key_index = 3;
@@ -4692,10 +4454,20 @@ class GenericCryptoKeyIdLengthTest : public GenericCryptoTest {
session_.FillSimpleMessage(kDuration, wvoec_mock::kControlAllowDecrypt,
kNoNonce);
// We are testing that the key ids do not have to have the same length.
session_.SetKeyId(0, "123456789012"); // 12 bytes (default key id length).
session_.SetKeyId(0, "123456789012"); // 12 bytes (common key id length).
session_.SetKeyId(1, "12345"); // short key id.
session_.SetKeyId(2, "123456789012-very-long-key-id");
ASSERT_EQ(2, kLongKeyId);
session_.SetKeyId(2, "1234567890123456"); // 16 byte key id. (default)
session_.SetKeyId(3, "12345678901234"); // 14 byte. (uncommon)
ASSERT_EQ(2u, kLongKeyId);
}
// Make all four keys have the same length.
void SetUniformKeyIdLength(size_t key_id_length) {
for(unsigned int i = 0; i < 4; i++) {
string key_id;
key_id.resize(key_id_length, i + 'a');
session_.SetKeyId(i, key_id);
}
}
void TestWithKey(unsigned int key_index) {
@@ -4738,6 +4510,16 @@ TEST_F(GenericCryptoKeyIdLengthTest, LongKeyId) {
TestWithKey(2);
}
TEST_F(GenericCryptoKeyIdLengthTest, UniformShortKeyId) {
SetUniformKeyIdLength(5);
TestWithKey(2);
}
TEST_F(GenericCryptoKeyIdLengthTest, UniformLongKeyId) {
SetUniformKeyIdLength(kTestKeyIdMaxLength);
TestWithKey(2);
}
TEST_F(OEMCryptoClientTest, UpdateUsageTableTest) {
EXPECT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable());
}

View File

@@ -1,5 +1,5 @@
export_variables = {
'CC': 'gcc',
'CXX': 'g++',
'CC': 'clang',
'CXX': 'clang++',
'AR': 'ar',
}

View File

@@ -12,6 +12,9 @@
'cflags': [
'-fPIC',
'-fno-exceptions',
# Enable all warnings, and treat warnings as errors.
'-Wall',
'-Werror',
],
# These are flags passed to the compiler for plain C only.

View File

@@ -1,4 +0,0 @@
#!/bin/bash
set -ex
./build.py x86-64 -r
./out/x86-64/Release/widevine_ce_cdm_unittest

View File

@@ -1 +0,0 @@
# dummy

View File

@@ -1 +0,0 @@
# dummy

View File

@@ -31,6 +31,20 @@
'<(protobuf_source)/src/google/protobuf/stubs/stringprintf.cc',
],
},
'target_defaults': {
# These flags silence warnings that appear in protobuf upstream.
# We will not maintain a divergent copy of protobuf to fix them.
'cflags': [
# Ignore unknown warning options, to support both gcc & clang at once.
'-Wno-unknown-warning-option',
# GCC:
'-Wno-sign-compare',
'-Wno-unused-local-typedefs',
# Clang:
'-Wno-unused-const-variable',
'-Wno-unused-function',
],
},
'targets': [
{
'target_name': 'protobuf_lite',

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1 +0,0 @@
# dummy

View File

@@ -1 +0,0 @@
# dummy

View File

@@ -1 +0,0 @@
// dummy