bug: 8620943 This is a merge of changes made to the Widevine CDM repository during certificate provisioning verification. The following changes are included: Fixes for certificate based licensing https://widevine-internal-review.googlesource.com/#/c/5162/ Base64 encode and decode now handles non-multiple of 24-bits input https://widevine-internal-review.googlesource.com/#/c/4981/ Fixed issues with device provisioning response handling https://widevine-internal-review.googlesource.com/#/c/5153/ Persistent storage to support device certificates https://widevine-internal-review.googlesource.com/#/c/5161/ Enable loading of certificates https://widevine-internal-review.googlesource.com/#/c/5172/ Provide license server url https://widevine-internal-review.googlesource.com/#/c/5173/ Change-Id: I0c032c1ae0055dcc1a7a77ad4b0ea0898030dc7d
324 lines
10 KiB
C++
324 lines
10 KiB
C++
// Copyright 2013 Google Inc. All Rights Reserved.
|
|
|
|
#include "string_conversions.h"
|
|
|
|
#include <ctype.h>
|
|
#include <iostream>
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <vector>
|
|
|
|
#include "log.h"
|
|
|
|
namespace {
|
|
/*
|
|
* Returns a 8-bit char that is mapped to the 6-bit base64 in_ch.
|
|
*
|
|
* Extracted from http://www.ietf.org/rfc/rfc3548.txt.
|
|
*
|
|
The "URL and Filename safe" Base 64 Alphabet
|
|
|
|
Value Encoding Value Encoding Value Encoding Value Encoding
|
|
0 A 17 R 34 i 51 z
|
|
1 B 18 S 35 j 52 0
|
|
2 C 19 T 36 k 53 1
|
|
3 D 20 U 37 l 54 2
|
|
4 E 21 V 38 m 55 3
|
|
5 F 22 W 39 n 56 4
|
|
6 G 23 X 40 o 57 5
|
|
7 H 24 Y 41 p 58 6
|
|
8 I 25 Z 42 q 59 7
|
|
9 J 26 a 43 r 60 8
|
|
10 K 27 b 44 s 61 9
|
|
11 L 28 c 45 t 62 - (minus)
|
|
12 M 29 d 46 u 63 _
|
|
13 N 30 e 47 v (underline)
|
|
14 O 31 f 48 w
|
|
15 P 32 g 49 x
|
|
16 Q 33 h 50 y (pad) =
|
|
*/
|
|
char B64ToBin(char in_ch) {
|
|
if (in_ch >= 'A' && in_ch <= 'Z') return in_ch - 'A';
|
|
if (in_ch >= 'a' && in_ch <= 'z') return in_ch - 'a' + 26;
|
|
if (in_ch >= '0' && in_ch <= '9') return in_ch - '0' + 52;
|
|
if (in_ch == '-') return 62;
|
|
if (in_ch == '_') return 63;
|
|
|
|
// arbitrary delimiter not in Base64 encoded alphabet, do not pick 0
|
|
return '?';
|
|
}
|
|
}
|
|
|
|
namespace wvcdm {
|
|
|
|
static bool CharToDigit(char ch, unsigned char* digit) {
|
|
if (ch >= '0' && ch <= '9') {
|
|
*digit = ch - '0';
|
|
} else {
|
|
ch = tolower(ch);
|
|
if ((ch >= 'a') && (ch <= 'f')) {
|
|
*digit = ch - 'a' + 10;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// converts an ascii hex string(2 bytes per digit) into a decimal byte string
|
|
std::vector<uint8_t> a2b_hex(const std::string& byte) {
|
|
std::vector<uint8_t> array;
|
|
unsigned int count = byte.size();
|
|
if (count == 0 || (count % 2) != 0) {
|
|
LOGE("Invalid input size %u for string %s", count, byte.c_str());
|
|
return array;
|
|
}
|
|
|
|
for (unsigned int i = 0; i < count / 2; ++i) {
|
|
unsigned char msb = 0; // most significant 4 bits
|
|
unsigned char lsb = 0; // least significant 4 bits
|
|
if (!CharToDigit(byte[i * 2], &msb) ||
|
|
!CharToDigit(byte[i * 2 + 1], &lsb)) {
|
|
LOGE("Invalid hex value %c%c at index %d", byte[i*2], byte[i*2+1], i);
|
|
return array;
|
|
}
|
|
array.push_back((msb << 4) | lsb);
|
|
}
|
|
return array;
|
|
}
|
|
|
|
std::string a2bs_hex(const std::string& byte) {
|
|
std::vector<uint8_t> array = a2b_hex(byte);
|
|
return std::string(array.begin(), array.end());
|
|
}
|
|
|
|
std::string b2a_hex(const std::vector<uint8_t>& byte) {
|
|
return HexEncode(&byte[0], byte.size());
|
|
}
|
|
|
|
std::string b2a_hex(const std::string& byte) {
|
|
return HexEncode(reinterpret_cast<const uint8_t *>(byte.data()),
|
|
byte.length());
|
|
}
|
|
|
|
// Filename-friendly base64 encoding (RFC4648), commonly referred as
|
|
// Base64WebSafeEncode.
|
|
// This is the encoding required by GooglePlay for certain
|
|
// license server transactions. It is also used for logging
|
|
// certain strings.
|
|
// The difference between web safe encoding vs regular encoding is that
|
|
// the web safe version replaces '+' with '-' and '/' with '_'.
|
|
std::string Base64SafeEncode(const std::vector<uint8_t>& bin_input) {
|
|
static const char kBase64Chars[] =
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
"abcdefghijklmnopqrstuvwxyz"
|
|
"0123456789-_";
|
|
if (bin_input.empty()) {
|
|
return std::string();
|
|
}
|
|
|
|
int in_size = bin_input.size();
|
|
int final_quantum_in_bytes = in_size % 3;
|
|
int full_in_chunks = in_size / 3;
|
|
int out_size = full_in_chunks * 4;
|
|
if (final_quantum_in_bytes) out_size += 4;
|
|
|
|
std::string b64_output(out_size, '\0');
|
|
int in_index = 0;
|
|
int out_index = 0;
|
|
unsigned long buffer;
|
|
unsigned char out_cc;
|
|
static const unsigned long kInMask = 0xff;
|
|
static const unsigned long kOutMask = 0x3f;
|
|
|
|
for (int i = 0; i < full_in_chunks; ++i) {
|
|
// up to 3 bytes (0..255) in
|
|
buffer = (bin_input.at(in_index) & kInMask);
|
|
buffer <<= 8;
|
|
buffer |= (++in_index >= in_size) ? 0 : (bin_input.at(in_index) & kInMask);
|
|
buffer <<= 8;
|
|
buffer |= (++in_index >= in_size) ? 0 : (bin_input.at(in_index) & kInMask);
|
|
++in_index;
|
|
|
|
// up to 4 bytes (0..63) out
|
|
out_cc = (buffer >> 18) & kOutMask;
|
|
b64_output.at(out_index) = kBase64Chars[out_cc];
|
|
if (++out_index >= out_size)
|
|
break;
|
|
out_cc = (buffer >> 12) & kOutMask;
|
|
b64_output.at(out_index) = kBase64Chars[out_cc];
|
|
if (++out_index >= out_size)
|
|
break;
|
|
out_cc = (buffer >> 6) & kOutMask;
|
|
b64_output.at(out_index) = kBase64Chars[out_cc];
|
|
if (++out_index >= out_size)
|
|
break;
|
|
out_cc = buffer & kOutMask;
|
|
b64_output.at(out_index) = kBase64Chars[out_cc];
|
|
++out_index;
|
|
}
|
|
|
|
if (final_quantum_in_bytes) {
|
|
switch(final_quantum_in_bytes) {
|
|
case 1: {
|
|
// reads 24-bits data, which is made up of one 8-bits char
|
|
buffer = (bin_input.at(in_index++) & kInMask);
|
|
buffer <<= 16;
|
|
|
|
// writes two 6-bits chars followed by two '=' padding char
|
|
out_cc = (buffer >> 18) & kOutMask;
|
|
b64_output.at(out_index++) = kBase64Chars[out_cc];
|
|
out_cc = (buffer >> 12) & kOutMask;
|
|
b64_output.at(out_index++) = kBase64Chars[out_cc];
|
|
b64_output.at(out_index++) = '=';
|
|
b64_output.at(out_index) = '=';
|
|
break;
|
|
}
|
|
case 2: {
|
|
// reads 24-bits data, which is made up of two 8-bits chars
|
|
buffer = (bin_input.at(in_index++) & kInMask);
|
|
buffer <<= 8;
|
|
buffer |= (bin_input.at(in_index++) & kInMask);
|
|
buffer <<= 8;
|
|
|
|
// writes three 6-bits chars followed by one '=' padding char
|
|
out_cc = (buffer >> 18) & kOutMask;
|
|
b64_output.at(out_index++) = kBase64Chars[out_cc];
|
|
out_cc = (buffer >> 12) & kOutMask;
|
|
b64_output.at(out_index++) = kBase64Chars[out_cc];
|
|
out_cc = (buffer >> 6) & kOutMask;
|
|
b64_output.at(out_index++) = kBase64Chars[out_cc];
|
|
b64_output.at(out_index) = '=';
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return b64_output;
|
|
}
|
|
|
|
// Decode for Filename-friendly base64 encoding (RFC4648), commonly referred
|
|
// as Base64WebSafeDecode.
|
|
// This is the encoding required by GooglePlay for certain
|
|
// license server transactions. It is also used for logging
|
|
// certain strings.
|
|
std::vector<uint8_t> Base64SafeDecode(const std::string& b64_input) {
|
|
if (b64_input.empty()) {
|
|
return std::vector<uint8_t>();
|
|
}
|
|
|
|
int in_size = b64_input.size();
|
|
int out_size = in_size;
|
|
std::vector<uint8_t> bin_output(out_size, '\0');
|
|
int in_index = 0;
|
|
int out_index = 0;
|
|
unsigned long buffer;
|
|
unsigned char out_cc;
|
|
static const unsigned long kOutMask = 0xff;
|
|
|
|
int counter = 0;
|
|
size_t delimiter_pos = b64_input.rfind('=');
|
|
if (delimiter_pos != std::string::npos) {
|
|
// Special case for partial last quantum indicated by '='
|
|
// at the end of encoded input.
|
|
counter = 1;
|
|
}
|
|
for (; counter < (in_size / 4); ++counter) {
|
|
// up to 4 bytes (0..63) in
|
|
buffer = B64ToBin(b64_input.at(in_index));
|
|
buffer <<= 6;
|
|
buffer |= (++in_index >= in_size) ? 0 : B64ToBin(b64_input.at(in_index));
|
|
buffer <<= 6;
|
|
buffer |= (++in_index >= in_size) ? 0 : B64ToBin(b64_input.at(in_index));
|
|
buffer <<= 6;
|
|
buffer |= (++in_index >= in_size) ? 0 : B64ToBin(b64_input.at(in_index));
|
|
++in_index;
|
|
// up to 3 bytes (0..255) out
|
|
out_cc = (buffer >> 16) & kOutMask;
|
|
bin_output.at(out_index) = out_cc;
|
|
if (++out_index >= out_size)
|
|
break;
|
|
out_cc = (buffer >> 8) & kOutMask;
|
|
bin_output.at(out_index) = out_cc;
|
|
if (++out_index >= out_size)
|
|
break;
|
|
out_cc = buffer & kOutMask;
|
|
bin_output.at(out_index) = out_cc;
|
|
++out_index;
|
|
}
|
|
|
|
if (delimiter_pos != std::string::npos) {
|
|
// it is either 2 chars plus 2 '=' or 3 chars plus one '='
|
|
buffer = B64ToBin(b64_input.at(in_index++));
|
|
buffer <<= 6;
|
|
buffer |= B64ToBin(b64_input.at(in_index++));
|
|
buffer <<= 6;
|
|
char special_char = b64_input.at(in_index++);
|
|
if ('=' == special_char) {
|
|
// we have 2 chars and 2 '='
|
|
buffer <<= 6;
|
|
out_cc = (buffer >> 16) & kOutMask;
|
|
bin_output.at(out_index++) = out_cc;
|
|
out_cc = (buffer >> 8) & kOutMask;
|
|
bin_output.at(out_index) = out_cc;
|
|
} else {
|
|
// we have 3 chars and 1 '='
|
|
buffer |= B64ToBin(special_char);
|
|
buffer <<= 6;
|
|
buffer |= B64ToBin(b64_input.at(in_index));
|
|
out_cc = (buffer >> 16) & kOutMask;
|
|
bin_output.at(out_index++) = out_cc;
|
|
out_cc = (buffer >> 8) & kOutMask;
|
|
bin_output.at(out_index++) = out_cc;
|
|
out_cc = buffer & kOutMask;
|
|
bin_output.at(out_index) = out_cc;
|
|
}
|
|
}
|
|
|
|
// adjust vector to reflect true size
|
|
bin_output.resize(out_index);
|
|
return bin_output;
|
|
}
|
|
|
|
std::string HexEncode(const uint8_t* in_buffer, unsigned int size) {
|
|
static const char kHexChars[] = "0123456789ABCDEF";
|
|
|
|
// Each input byte creates two output hex characters.
|
|
std::string out_buffer(size * 2, '\0');
|
|
|
|
for (unsigned int i = 0; i < size; ++i) {
|
|
char byte = in_buffer[i];
|
|
out_buffer[(i << 1)] = kHexChars[(byte >> 4) & 0xf];
|
|
out_buffer[(i << 1) + 1] = kHexChars[byte & 0xf];
|
|
}
|
|
return out_buffer;
|
|
}
|
|
|
|
std::string IntToString(int value) {
|
|
// log10(2) ~= 0.3 bytes needed per bit or per byte log10(2**8) ~= 2.4.
|
|
// So round up to allocate 3 output characters per byte, plus 1 for '-'.
|
|
const int kOutputBufSize = 3 * sizeof(int) + 1;
|
|
char buffer[kOutputBufSize];
|
|
memset(buffer, 0, kOutputBufSize);
|
|
snprintf(buffer, kOutputBufSize, "%d", value);
|
|
|
|
std::string out_string(buffer, sizeof(buffer));
|
|
return out_string;
|
|
}
|
|
|
|
std::string UintToString(unsigned int value) {
|
|
// log10(2) ~= 0.3 bytes needed per bit or per byte log10(2**8) ~= 2.4.
|
|
// So round up to allocate 3 output characters per byte.
|
|
const int kOutputBufSize = 3 * sizeof(unsigned int);
|
|
char buffer[kOutputBufSize];
|
|
memset(buffer, 0, kOutputBufSize);
|
|
snprintf(buffer, kOutputBufSize, "%u", value);
|
|
|
|
std::string out_string(buffer, sizeof(buffer));
|
|
return out_string;
|
|
}
|
|
|
|
}; // namespace wvcdm
|