Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  ClearKeyUtils.cpp   Sprache: C

 
/*
 * Copyright 2015, Mozilla Foundation and contributors
 *
 * 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 "ClearKeyUtils.h"

#include <assert.h>
#include <ctype.h>
#include <memory.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

#include <algorithm>
#include <cctype>
#include <memory>
#include <sstream>
#include <vector>

#include "pk11pub.h"
#include "prerror.h"
#include "secmodt.h"

#include "BigEndian.h"
#include "ClearKeyBase64.h"
#include "mozilla/Sprintf.h"
#include "psshparser/PsshParser.h"

using namespace cdm;
using std::string;
using std::stringstream;
using std::vector;

struct DeleteHelper {
  void operator()(PK11Context* value) { PK11_DestroyContext(value, true); }
  void operator()(PK11SlotInfo* value) { PK11_FreeSlot(value); }
  void operator()(PK11SymKey* value) { PK11_FreeSymKey(value); }
};

template <class T>
struct MaybeDeleteHelper {
  void operator()(T* ptr) {
    if (ptr) {
      DeleteHelper del;
      del(ptr);
    }
  }
};

void CK_Log(const char* aFmt, ...) {
  FILE* out = stdout;

  if (getenv("CLEARKEY_LOG_FILE")) {
    out = fopen(getenv("CLEARKEY_LOG_FILE"), "a");
  }

  va_list ap;

  va_start(ap, aFmt);
  const size_t len = 1024;
  char buf[len];
  VsprintfLiteral(buf, aFmt, ap);
  va_end(ap);

  fprintf(out, "%s\n", buf);
  fflush(out);

  if (out != stdout) {
    fclose(out);
  }
}

static bool PrintableAsString(const uint8_t* aBytes, uint32_t aLength) {
  return std::all_of(aBytes, aBytes + aLength,
                     [](uint8_t c) { return isprint(c) == 1; });
}

void CK_LogArray(const char* prepend, const uint8_t* aData,
                 const uint32_t aDataSize) {
  // If the data is valid ascii, use that. Otherwise print the hex
  string data = PrintableAsString(aData, aDataSize)
                    ? string(aData, aData + aDataSize)
                    : ClearKeyUtils::ToHexString(aData, aDataSize);

  CK_LOGD("%s%s", prepend, data.c_str());
}

/* static */
bool ClearKeyUtils::DecryptCbcs(const vector<uint8_t>& aKey,
                                const vector<uint8_t>& aIV,
                                mozilla::Span<uint8_t> aSubsample,
                                uint32_t aCryptByteBlock,
                                uint32_t aSkipByteBlock) {
  if (aKey.size() != CENC_KEY_LEN || aIV.size() != CENC_KEY_LEN) {
    CK_LOGE("Key and IV size should be 16!");
    return false;
  }

  if (aSubsample.Length() == 0) {
    // Nothing to decrypt.
    return true;
  }

  std::unique_ptr<PK11SlotInfo, MaybeDeleteHelper<PK11SlotInfo>> slot(
      PK11_GetInternalKeySlot());

  if (!slot.get()) {
    CK_LOGE("Failed to get internal PK11 slot");
    return false;
  }

  SECItem keyItem = {siBuffer, (unsigned char*)&aKey[0], CENC_KEY_LEN};
  SECItem ivItem = {siBuffer, (unsigned char*)&aIV[0], CENC_KEY_LEN};

  std::unique_ptr<PK11SymKey, MaybeDeleteHelper<PK11SymKey>> key(
      PK11_ImportSymKey(slot.get(), CKM_AES_CBC, PK11_OriginUnwrap, CKA_DECRYPT,
                        &keyItem, nullptr));

  if (!key.get()) {
    CK_LOGE("Failed to import sym key");
    return false;
  }

  std::unique_ptr<PK11Context, MaybeDeleteHelper<PK11Context>> ctx(
      PK11_CreateContextBySymKey(CKM_AES_CBC, CKA_DECRYPT, key.get(), &ivItem));

  if (!ctx) {
    CK_LOGE("Failed to get PK11Context!");
    return false;
  }

  assert(aCryptByteBlock <= 0xFF);
  assert(aSkipByteBlock <= 0xFF);

  uint8_t* encryptedSubsample = &aSubsample[0];
  const uint32_t BLOCK_SIZE = 16;
  const uint32_t skipBytes = aSkipByteBlock * BLOCK_SIZE;
  const uint32_t totalBlocks = aSubsample.Length() / BLOCK_SIZE;
  uint32_t blocksProcessed = 0;

  if (aSkipByteBlock == 0) {
    // ISO/IEC 23001 - 7 Section 9.6.1
    // 'When the fields default_crypt_byte_block and default_skip_byte_block in
    // a version 1 Track Encryption Box('tenc') are non - zero numbers, pattern
    // encryption SHALL be applied.'
    // So if both are 0, then everything is encrypted. Similarly, if skip is 0
    // and crypt is non-0, everything is encrypted.
    // In this case we can just decrypt all the blocks in one call. This is the
    // same outcome as decrypting them using smaller steps, as either way the
    // CBC result should be the same.
    MOZ_ASSERT(skipBytes == 0);
    aCryptByteBlock = totalBlocks;
  }

  while (blocksProcessed < totalBlocks) {
    uint32_t blocksToDecrypt = aCryptByteBlock <= totalBlocks - blocksProcessed
                                   ? aCryptByteBlock
                                   : totalBlocks - blocksProcessed;
    uint32_t bytesToDecrypt = blocksToDecrypt * BLOCK_SIZE;
    int outLen;
    SECStatus rv;
    rv = PK11_CipherOp(ctx.get(), encryptedSubsample, &outLen, bytesToDecrypt,
                       encryptedSubsample, bytesToDecrypt);
    if (rv != SECSuccess) {
      CK_LOGE("PK11_CipherOp() failed");
      return false;
    }

    encryptedSubsample += skipBytes + bytesToDecrypt;
    blocksProcessed += aSkipByteBlock + blocksToDecrypt;
  }

  return true;
}

/* static */
bool ClearKeyUtils::DecryptAES(const vector<uint8_t>& aKey,
                               vector<uint8_t>& aData, vector<uint8_t>& aIV) {
  assert(aIV.size() == CENC_KEY_LEN);
  assert(aKey.size() == CENC_KEY_LEN);

  PK11SlotInfo* slot = PK11_GetInternalKeySlot();
  if (!slot) {
    CK_LOGE("Failed to get internal PK11 slot");
    return false;
  }

  SECItem keyItem = {siBuffer, (unsigned char*)&aKey[0], CENC_KEY_LEN};
  PK11SymKey* key = PK11_ImportSymKey(slot, CKM_AES_CTR, PK11_OriginUnwrap,
                                      CKA_ENCRYPT, &keyItem, nullptr);
  PK11_FreeSlot(slot);
  if (!key) {
    CK_LOGE("Failed to import sym key");
    return false;
  }

  CK_AES_CTR_PARAMS params;
  params.ulCounterBits = 32;
  memcpy(¶ms.cb, &aIV[0], CENC_KEY_LEN);
  SECItem paramItem = {siBuffer, (unsigned char*)¶ms,
                       sizeof(CK_AES_CTR_PARAMS)};

  unsigned int outLen = 0;
  auto rv = PK11_Decrypt(key, CKM_AES_CTR, ¶mItem, &aData[0], &outLen,
                         aData.size(), &aData[0], aData.size());

  aData.resize(outLen);
  PK11_FreeSymKey(key);

  if (rv != SECSuccess) {
    CK_LOGE("PK11_Decrypt() failed");
    return false;
  }

  return true;
}

/**
 * ClearKey expects all Key IDs to be base64 encoded with non-standard alphabet
 * and padding.
 */

static bool EncodeBase64Web(vector<uint8_t> aBinary, string& aEncoded) {
  const char sAlphabet[] =
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
  const uint8_t sMask = 0x3f;

  aEncoded.resize((aBinary.size() * 8 + 5) / 6);

  // Pad binary data in case there's rubbish past the last byte.
  aBinary.push_back(0);

  // Number of bytes not consumed in the previous character
  uint32_t shift = 0;

  auto out = aEncoded.begin();
  auto data = aBinary.begin();
  for (string::size_type i = 0; i < aEncoded.length(); i++) {
    if (shift) {
      out[i] = (*data << (6 - shift)) & sMask;
      data++;
    } else {
      out[i] = 0;
    }

    out[i] += (*data >> (shift + 2)) & sMask;
    shift = (shift + 2) % 8;

    // Cast idx to size_t before using it as an array-index,
    // to pacify clang 'Wchar-subscripts' warning:
    size_t idx = static_cast<size_t>(out[i]);

    // out of bounds index for 'sAlphabet'
    assert(idx < std::size(sAlphabet));
    out[i] = sAlphabet[idx];
  }

  return true;
}

/* static */
void ClearKeyUtils::MakeKeyRequest(const vector<KeyId>& aKeyIDs,
                                   string& aOutRequest,
                                   SessionType aSessionType) {
  assert(!aKeyIDs.empty() && aOutRequest.empty());

  aOutRequest.append("{\"kids\":[");
  for (size_t i = 0; i < aKeyIDs.size(); i++) {
    if (i) {
      aOutRequest.append(",");
    }
    aOutRequest.append("\"");

    string base64key;
    EncodeBase64Web(aKeyIDs[i], base64key);
    aOutRequest.append(base64key);

    aOutRequest.append("\"");
  }
  aOutRequest.append("],\"type\":");

  aOutRequest.append("\"");
  aOutRequest.append(SessionTypeToString(aSessionType));
  aOutRequest.append("\"}");
}

#define EXPECT_SYMBOL(CTX, X)                     \
  do {                                            \
    if (GetNextSymbol(CTX) != (X)) {              \
      CK_LOGE("Unexpected symbol in JWK parser"); \
      return false;                               \
    }                                             \
  } while (false)

struct ParserContext {
  const uint8_t* mIter;
  const uint8_t* mEnd;
};

static uint8_t PeekSymbol(ParserContext& aCtx) {
  for (; aCtx.mIter < aCtx.mEnd; (aCtx.mIter)++) {
    if (!isspace(*aCtx.mIter)) {
      return *aCtx.mIter;
    }
  }

  return 0;
}

static uint8_t GetNextSymbol(ParserContext& aCtx) {
  uint8_t sym = PeekSymbol(aCtx);
  aCtx.mIter++;
  return sym;
}

static bool SkipToken(ParserContext& aCtx);

static bool SkipString(ParserContext& aCtx) {
  EXPECT_SYMBOL(aCtx, '"');
  for (uint8_t sym = GetNextSymbol(aCtx); sym; sym = GetNextSymbol(aCtx)) {
    if (sym == '\\') {
      sym = GetNextSymbol(aCtx);
      if (!sym) {
        return false;
      }
    } else if (sym == '"') {
      return true;
    }
  }

  return false;
}

/**
 * Skip whole object and values it contains.
 */

static bool SkipObject(ParserContext& aCtx) {
  EXPECT_SYMBOL(aCtx, '{');

  if (PeekSymbol(aCtx) == '}') {
    GetNextSymbol(aCtx);
    return true;
  }

  while (true) {
    if (!SkipString(aCtx)) return false;
    EXPECT_SYMBOL(aCtx, ':');
    if (!SkipToken(aCtx)) return false;

    if (PeekSymbol(aCtx) == '}') {
      GetNextSymbol(aCtx);
      return true;
    }
    EXPECT_SYMBOL(aCtx, ',');
  }
}

/**
 * Skip array value and the values it contains.
 */

static bool SkipArray(ParserContext& aCtx) {
  EXPECT_SYMBOL(aCtx, '[');

  if (PeekSymbol(aCtx) == ']') {
    GetNextSymbol(aCtx);
    return true;
  }

  while (SkipToken(aCtx)) {
    if (PeekSymbol(aCtx) == ']') {
      GetNextSymbol(aCtx);
      return true;
    }
    EXPECT_SYMBOL(aCtx, ',');
  }

  return false;
}

/**
 * Skip unquoted literals like numbers, |true|, and |null|.
 * (XXX and anything else that matches /([:alnum:]|[+-.])+/)
 */

static bool SkipLiteral(ParserContext& aCtx) {
  for (; aCtx.mIter < aCtx.mEnd; aCtx.mIter++) {
    if (!isalnum(*aCtx.mIter) && *aCtx.mIter != '.' && *aCtx.mIter != '-' &&
        *aCtx.mIter != '+') {
      return true;
    }
  }

  return false;
}

static bool SkipToken(ParserContext& aCtx) {
  uint8_t startSym = PeekSymbol(aCtx);
  if (startSym == '"') {
    CK_LOGD("JWK parser skipping string");
    return SkipString(aCtx);
  } else if (startSym == '{') {
    CK_LOGD("JWK parser skipping object");
    return SkipObject(aCtx);
  } else if (startSym == '[') {
    CK_LOGD("JWK parser skipping array");
    return SkipArray(aCtx);
  } else {
    CK_LOGD("JWK parser skipping literal");
    return SkipLiteral(aCtx);
  }
}

static bool GetNextLabel(ParserContext& aCtx, string& aOutLabel) {
  EXPECT_SYMBOL(aCtx, '"');

  const uint8_t* start = aCtx.mIter;
  for (uint8_t sym = GetNextSymbol(aCtx); sym; sym = GetNextSymbol(aCtx)) {
    if (sym == '\\') {
      GetNextSymbol(aCtx);
      continue;
    }

    if (sym == '"') {
      aOutLabel.assign(start, aCtx.mIter - 1);
      return true;
    }
  }

  return false;
}

static bool ParseKeyObject(ParserContext& aCtx, KeyIdPair& aOutKey) {
  EXPECT_SYMBOL(aCtx, '{');

  // Reject empty objects as invalid licenses.
  if (PeekSymbol(aCtx) == '}') {
    GetNextSymbol(aCtx);
    return false;
  }

  string keyId;
  string key;

  while (true) {
    string label;
    string value;

    if (!GetNextLabel(aCtx, label)) {
      return false;
    }

    EXPECT_SYMBOL(aCtx, ':');
    if (label == "kty") {
      if (!GetNextLabel(aCtx, value)) return false;
      // By spec, type must be "oct".
      if (value != "oct"return false;
    } else if (label == "k" && PeekSymbol(aCtx) == '"') {
      // if this isn't a string we will fall through to the SkipToken() path.
      if (!GetNextLabel(aCtx, key)) return false;
    } else if (label == "kid" && PeekSymbol(aCtx) == '"') {
      if (!GetNextLabel(aCtx, keyId)) return false;
    } else {
      if (!SkipToken(aCtx)) return false;
    }

    uint8_t sym = PeekSymbol(aCtx);
    if (!sym || sym == '}') {
      break;
    }
    EXPECT_SYMBOL(aCtx, ',');
  }

  return !key.empty() && !keyId.empty() &&
         DecodeBase64(keyId, aOutKey.mKeyId) &&
         DecodeBase64(key, aOutKey.mKey) && GetNextSymbol(aCtx) == '}';
}

static bool ParseKeys(ParserContext& aCtx, vector<KeyIdPair>& aOutKeys) {
  // Consume start of array.
  EXPECT_SYMBOL(aCtx, '[');

  while (true) {
    KeyIdPair key;
    if (!ParseKeyObject(aCtx, key)) {
      CK_LOGE("Failed to parse key object");
      return false;
    }

    assert(!key.mKey.empty() && !key.mKeyId.empty());
    aOutKeys.push_back(key);

    uint8_t sym = PeekSymbol(aCtx);
    if (!sym || sym == ']') {
      break;
    }

    EXPECT_SYMBOL(aCtx, ',');
  }

  return GetNextSymbol(aCtx) == ']';
}

/* static */
bool ClearKeyUtils::ParseJWK(const uint8_t* aKeyData, uint32_t aKeyDataSize,
                             vector<KeyIdPair>& aOutKeys,
                             SessionType aSessionType) {
  ParserContext ctx;
  ctx.mIter = aKeyData;
  ctx.mEnd = aKeyData + aKeyDataSize;

  // Consume '{' from start of object.
  EXPECT_SYMBOL(ctx, '{');

  while (true) {
    string label;
    // Consume member key.
    if (!GetNextLabel(ctx, label)) return false;
    EXPECT_SYMBOL(ctx, ':');

    if (label == "keys") {
      // Parse "keys" array.
      if (!ParseKeys(ctx, aOutKeys)) return false;
    } else if (label == "type") {
      // Consume type string.
      string type;
      if (!GetNextLabel(ctx, type)) return false;
      if (type != SessionTypeToString(aSessionType)) {
        return false;
      }
    } else {
      SkipToken(ctx);
    }

    // Check for end of object.
    if (PeekSymbol(ctx) == '}') {
      break;
    }

    // Consume ',' between object members.
    EXPECT_SYMBOL(ctx, ',');
  }

  // Consume '}' from end of object.
  EXPECT_SYMBOL(ctx, '}');

  return true;
}

static bool ParseKeyIds(ParserContext& aCtx, vector<KeyId>& aOutKeyIds) {
  // Consume start of array.
  EXPECT_SYMBOL(aCtx, '[');

  while (true) {
    string label;
    vector<uint8_t> keyId;
    if (!GetNextLabel(aCtx, label) || !DecodeBase64(label, keyId)) {
      return false;
    }
    if (!keyId.empty() && keyId.size() <= kMaxKeyIdsLength) {
      aOutKeyIds.push_back(keyId);
    }

    uint8_t sym = PeekSymbol(aCtx);
    if (!sym || sym == ']') {
      break;
    }

    EXPECT_SYMBOL(aCtx, ',');
  }

  return GetNextSymbol(aCtx) == ']';
}

/* static */
bool ClearKeyUtils::ParseKeyIdsInitData(const uint8_t* aInitData,
                                        uint32_t aInitDataSize,
                                        vector<KeyId>& aOutKeyIds) {
  ParserContext ctx;
  ctx.mIter = aInitData;
  ctx.mEnd = aInitData + aInitDataSize;

  // Consume '{' from start of object.
  EXPECT_SYMBOL(ctx, '{');

  while (true) {
    string label;
    // Consume member kids.
    if (!GetNextLabel(ctx, label)) return false;
    EXPECT_SYMBOL(ctx, ':');

    if (label == "kids") {
      // Parse "kids" array.
      if (!ParseKeyIds(ctx, aOutKeyIds) || aOutKeyIds.empty()) {
        return false;
      }
    } else {
      SkipToken(ctx);
    }

    // Check for end of object.
    if (PeekSymbol(ctx) == '}') {
      break;
    }

    // Consume ',' between object members.
    EXPECT_SYMBOL(ctx, ',');
  }

  // Consume '}' from end of object.
  EXPECT_SYMBOL(ctx, '}');

  return true;
}

/* static */ const char* ClearKeyUtils::SessionTypeToString(
    SessionType aSessionType) {
  switch (aSessionType) {
    case SessionType::kTemporary:
      return "temporary";
    case SessionType::kPersistentLicense:
      return "persistent-license";
    default: {
      // We don't support any other license types.
      assert(false);
      return "invalid";
    }
  }
}

/* static */
bool ClearKeyUtils::IsValidSessionId(const char* aBuff, uint32_t aLength) {
  if (aLength > 10) {
    // 10 is the max number of characters in UINT32_MAX when
    // represented as a string; ClearKey session ids are integers.
    return false;
  }
  for (uint32_t i = 0; i < aLength; i++) {
    if (!isdigit(aBuff[i])) {
      return false;
    }
  }
  return true;
}

string ClearKeyUtils::ToHexString(const uint8_t* aBytes, uint32_t aLength) {
  stringstream ss;
  ss << std::showbase << std::uppercase << std::hex;
  for (uint32_t i = 0; i < aLength; ++i) {
    ss << std::hex << static_cast<uint32_t>(aBytes[i]);
    ss << " ";
  }

  return ss.str();
}

Messung V0.5
C=93 H=89 G=90

¤ Dauer der Verarbeitung: 0.14 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge