Full decrypt path testing

Merge from master branch of Widevine repo of http://go/wvgerrit/66080
Merge from oemcrypto-v15 branch of Widevine repo of http://go/wvgerrit/64002

This CL updates OEMCrypto reference code and unit tests to support full decrypt
path testing.

Test: unit tests
Test: tested as part of http://go/ag/5501993
Bug: 34078913
Change-Id: Ia67374599d6619698a336f41513068ad04294e7f
This commit is contained in:
Fred Gylys-Colwell
2018-11-12 14:21:17 -08:00
parent 4ffacfdcc7
commit 246621c5ce
12 changed files with 287 additions and 5 deletions

View File

@@ -55,6 +55,7 @@ OEMCryptoResult OEMCrypto_CreateOldUsageEntry(SecurityLevel level,
uint32_t OEMCrypto_GetAnalogOutputFlags(SecurityLevel level);
const char* OEMCrypto_BuildInformation(SecurityLevel level);
uint32_t OEMCrypto_ResourceRatingTier(SecurityLevel level);
uint32_t OEMCrypto_SupportsDecryptHash(SecurityLevel level);
} // namespace wvcdm
/* The following functions are deprecated in OEMCrypto v13. They are defined

View File

@@ -277,6 +277,15 @@ typedef OEMCryptoResult (*L1_CreateOldUsageEntry_t)(
typedef uint32_t (*L1_GetAnalogOutputFlags_t)(void);
typedef const char* (*L1_BuildInformation_t)(void);
typedef uint32_t (*L1_ResourceRatingTier_t)(void);
typedef uint32_t (*L1_SupportsDecryptHash_t)(void);
typedef OEMCryptoResult (*L1_InitializeDecryptHash_t)(
OEMCrypto_SESSION session);
typedef OEMCryptoResult (*L1_SetDecryptHash_t)(OEMCrypto_SESSION session,
uint32_t frame_number,
const uint8_t* hash,
size_t hash_length);
typedef OEMCryptoResult (*L1_GetHashErrorCode_t)(OEMCrypto_SESSION session,
uint32_t* failed_frame_number);
struct FunctionPointers {
uint32_t version;
@@ -347,6 +356,10 @@ struct FunctionPointers {
L1_GetAnalogOutputFlags_t GetAnalogOutputFlags;
L1_BuildInformation_t BuildInformation;
L1_ResourceRatingTier_t ResourceRatingTier;
L1_SupportsDecryptHash_t SupportsDecryptHash;
L1_InitializeDecryptHash_t InitializeDecryptHash;
L1_SetDecryptHash_t SetDecryptHash;
L1_GetHashErrorCode_t GetHashErrorCode;
L1_LoadKeys_V8_t LoadKeys_V8;
L1_GenerateRSASignature_V8_t GenerateRSASignature_V8;
@@ -766,6 +779,10 @@ class Adapter {
LOOKUP_ALL(13, UpdateUsageEntry, OEMCrypto_UpdateUsageEntry);
LOOKUP( 9, 12, UpdateUsageTable, OEMCrypto_UpdateUsageTable);
LOOKUP_ALL( 8, WrapKeybox, OEMCrypto_WrapKeybox);
LOOKUP_ALL(15, SupportsDecryptHash, OEMCrypto_SupportsDecryptHash);
LOOKUP_ALL(15, InitializeDecryptHash, OEMCrypto_InitializeDecryptHash);
LOOKUP_ALL(15, SetDecryptHash, OEMCrypto_SetDecryptHash);
LOOKUP_ALL(15, GetHashErrorCode, OEMCrypto_GetHashErrorCode);
// clang-format on
// If the keybox or oem certificate is valid, we are done.
@@ -902,6 +919,10 @@ class Adapter {
level3_.MoveEntry = Level3_MoveEntry;
level3_.CopyOldUsageEntry = Level3_CopyOldUsageEntry;
level3_.CreateOldUsageEntry = Level3_CreateOldUsageEntry;
// TODO(srujzs) level3_.SupportsDecryptHash = Level3_SupportsDecryptHash;
// TODO(srujzs) level3_.InitializeDecryptHash = Level3_InitializeDecryptHash;
// TODO(srujzs) level3_.SetDecryptHash = Level3_SetDecryptHash;
// TODO(srujzs) level3_.GetHashErrorCode = Level3_GetHashErrorCode;
// clang-format on
level3_.version = Level3_APIVersion();
@@ -1128,6 +1149,15 @@ uint32_t OEMCrypto_ResourceRatingTier(SecurityLevel level) {
return fcn->ResourceRatingTier();
}
uint32_t OEMCrypto_SupportsDecryptHash(SecurityLevel level) {
if (!gAdapter.get()) return OEMCrypto_Hash_Not_Supported;
const FunctionPointers* fcn = gAdapter->GetFunctionPointers(level);
if (!fcn) return OEMCrypto_Hash_Not_Supported;
if (fcn->version < 14) return OEMCrypto_Hash_Not_Supported;
if (fcn->BuildInformation == NULL) return OEMCrypto_Hash_Not_Supported;
return fcn->SupportsDecryptHash();
}
bool OEMCrypto_SupportsUsageTable(SecurityLevel level) {
if (!gAdapter.get()) return false;
const FunctionPointers* fcn = gAdapter->GetFunctionPointers(level);
@@ -2307,3 +2337,42 @@ extern "C" OEMCryptoResult OEMCrypto_CreateOldUsageEntry(
time_since_last_decrypt, status, server_mac_key, client_mac_key, pst,
pst_length);
}
extern "C" uint32_t OEMCrypto_SupportsDecryptHash(){
return OEMCrypto_SupportsDecryptHash(kLevelDefault);
}
extern "C" OEMCryptoResult OEMCrypto_InitializeDecryptHash(
OEMCrypto_SESSION session) {
if (!gAdapter.get()) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
LevelSession pair = gAdapter->GetSession(session);
if (!pair.fcn) return OEMCrypto_ERROR_INVALID_SESSION;
if (pair.fcn->InitializeDecryptHash == NULL) {
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
}
return pair.fcn->InitializeDecryptHash(pair.session);
}
extern "C" OEMCryptoResult OEMCrypto_SetDecryptHash(OEMCrypto_SESSION session,
uint32_t frame_number,
const uint8_t* hash,
size_t hash_length) {
if (!gAdapter.get()) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
LevelSession pair = gAdapter->GetSession(session);
if (!pair.fcn) return OEMCrypto_ERROR_INVALID_SESSION;
if (pair.fcn->SetDecryptHash == NULL) {
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
}
return pair.fcn->SetDecryptHash(pair.session, frame_number, hash, hash_length);
}
extern "C" OEMCryptoResult OEMCrypto_GetHashErrorCode(OEMCrypto_SESSION session,
uint32_t* failed_frame_number) {
if (!gAdapter.get()) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
LevelSession pair = gAdapter->GetSession(session);
if (!pair.fcn) return OEMCrypto_ERROR_INVALID_SESSION;
if (pair.fcn->GetHashErrorCode == NULL) {
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
}
return pair.fcn->GetHashErrorCode(pair.session, failed_frame_number);
}

View File

@@ -28,6 +28,7 @@ const uint32_t kControlObserveDataPath = (1<<31);
const uint32_t kControlObserveHDCP = (1<<30);
const uint32_t kControlObserveCGMS = (1<<29);
const uint32_t kControlRequireAntiRollbackHardware = (1<<28);
const uint32_t kControlAllowHashVerification = (1<<24);
const uint32_t kSharedLicense = (1<<23);
const uint32_t kControlSRMVersionRequired = (1<<22);
const uint32_t kControlDisableAnalogOutput = (1<<21);

View File

@@ -1617,4 +1617,52 @@ extern "C" OEMCryptoResult OEMCrypto_CreateOldUsageEntry(
pst_length);
}
extern "C" uint32_t OEMCrypto_SupportsDecryptHash() {
return OEMCrypto_CRC_Clear_Buffer;
}
extern "C" OEMCryptoResult OEMCrypto_InitializeDecryptHash(
OEMCrypto_SESSION session) {
if (!crypto_engine) {
LOGE("OEMCrypto_InitializeDecryptHash: OEMCrypto Not Initialized.");
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
SessionContext* session_ctx = crypto_engine->FindSession(session);
if (!session_ctx || !session_ctx->isValid()) {
LOGE("[OEMCrypto_InitializeDecryptHash(): ERROR_INVALID_SESSION]");
return OEMCrypto_ERROR_INVALID_SESSION;
}
return session_ctx->InitializeDecryptHash();
}
extern "C" OEMCryptoResult OEMCrypto_SetDecryptHash(OEMCrypto_SESSION session,
uint32_t frame_number,
const uint8_t* hash,
size_t hash_length) {
if (!crypto_engine) {
LOGE("OEMCrypto_SetDecryptHash: OEMCrypto Not Initialized.");
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
SessionContext* session_ctx = crypto_engine->FindSession(session);
if (!session_ctx || !session_ctx->isValid()) {
LOGE("[OEMCrypto_SetDecryptHash(): ERROR_INVALID_SESSION]");
return OEMCrypto_ERROR_INVALID_SESSION;
}
return session_ctx->SetDecryptHash(frame_number, hash, hash_length);
}
extern "C" OEMCryptoResult OEMCrypto_GetHashErrorCode(
OEMCrypto_SESSION session, uint32_t* failed_frame_number) {
if (!crypto_engine) {
LOGE("OEMCrypto_GetHashErrorCode: OEMCrypto Not Initialized.");
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
SessionContext* session_ctx = crypto_engine->FindSession(session);
if (!session_ctx || !session_ctx->isValid()) {
LOGE("[OEMCrypto_GetHashErrorCode(): ERROR_INVALID_SESSION]");
return OEMCrypto_ERROR_INVALID_SESSION;
}
return session_ctx->GetHashErrorCode(failed_frame_number);
}
} // namespace wvoec_ref

View File

@@ -32,6 +32,7 @@
#include "oemcrypto_types.h"
#include "disallow_copy_and_assign.h"
#include "string_conversions.h"
#include "wvcrc32.h"
static const int kPssSaltLength = 20;
@@ -1167,6 +1168,7 @@ OEMCryptoResult SessionContext::SelectContentKey(
LOGE("No key matches key id");
return OEMCrypto_ERROR_NO_CONTENT_KEY;
}
compute_hash_ = false;
content_key->set_ctr_mode(cipher_mode == OEMCrypto_CipherMode_CTR);
current_content_key_ = content_key;
const KeyControlBlock& control = current_content_key()->control();
@@ -1284,6 +1286,29 @@ OEMCryptoResult SessionContext::DecryptCENC(
const OEMCrypto_CENCEncryptPatternDesc* pattern, const uint8_t* cipher_data,
size_t cipher_data_length, bool is_encrypted, uint8_t* clear_data,
OEMCryptoBufferType buffer_type) {
OEMCryptoResult result =
ChooseDecrypt(iv, block_offset, pattern, cipher_data, cipher_data_length,
is_encrypted, clear_data, buffer_type);
if (compute_hash_) {
if (current_content_key() == NULL ||
(current_content_key()->control().control_bits() &
wvoec::kControlAllowHashVerification) == 0) {
// This should not happen: this check should already have occured in
// InitializeDecryptHash or the hash should have been discarded in
// SelectContentKey. But it doesn't hurt to double check.
LOGE("[DecryptCENC(): OEMCrypto_ERROR_UNKNOWN_FAILURE]");
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
current_hash_ = wvcrc32Cont(clear_data, cipher_data_length, current_hash_);
}
return result;
}
OEMCryptoResult SessionContext::ChooseDecrypt(
const uint8_t* iv, size_t block_offset,
const OEMCrypto_CENCEncryptPatternDesc* pattern, const uint8_t* cipher_data,
size_t cipher_data_length, bool is_encrypted, uint8_t* clear_data,
OEMCryptoBufferType buffer_type) {
// If the data is clear, we do not need a current key selected.
if (!is_encrypted) {
if (buffer_type != OEMCrypto_BufferType_Direct) {
@@ -1501,4 +1526,62 @@ OEMCryptoResult SessionContext::DecryptCTR(const uint8_t* key_u8,
return OEMCrypto_SUCCESS;
}
OEMCryptoResult SessionContext::InitializeDecryptHash() {
// Check there is a content key, and it is allowed.
if (current_content_key() == NULL ||
(current_content_key()->control().control_bits() &
wvoec::kControlAllowHashVerification) == 0) {
LOGE("[InitializeDecryptHash(): OEMCrypto_ERROR_UNKNOWN_FAILURE]");
compute_hash_ = false;
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
compute_hash_ = true;
current_hash_ = wvcrc32Init();
return OEMCrypto_SUCCESS;
}
OEMCryptoResult SessionContext::SetDecryptHash(uint32_t frame_number,
const uint8_t* hash,
size_t hash_length) {
// Check there is a content key, and it is allowed.
if (current_content_key() == NULL ||
(current_content_key()->control().control_bits() &
wvoec::kControlAllowHashVerification) == 0) {
LOGE("[SetDecryptHash(): OEMCrypto_ERROR_UNKNOWN_FAILURE]");
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (!compute_hash_) {
// This would happen if somebody computes the hash, and then changes keys.
LOGE("[SetDecryptHash(): OEMCrypto_ERROR_UNKNOWN_FAILURE]");
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
compute_hash_ = false;
if (hash_length < sizeof(uint32_t)) {
LOGE("[SetDecryptHash(): short buffer]");
return OEMCrypto_ERROR_SHORT_BUFFER;
}
if (hash_length > sizeof(uint32_t)) {
LOGE("[SetDecryptHash(): long buffer]");
return OEMCrypto_ERROR_BUFFER_TOO_LARGE;
}
uint32_t given_hash = *reinterpret_cast<const uint32_t*>(hash);
if (current_hash_ != given_hash) {
LOGE("CRC for frame %d is %08x, should be %08x\n", frame_number,
current_hash_, given_hash);
// Update bad_frame_number_ only if this is the first bad frame.
if (hash_error_ == OEMCrypto_SUCCESS) bad_frame_number_ = frame_number;
hash_error_ = OEMCrypto_ERROR_BAD_HASH;
}
// Return success if the hash was compared, even if there was an error.
return OEMCrypto_SUCCESS;
}
OEMCryptoResult SessionContext::GetHashErrorCode(
uint32_t* failed_frame_number) {
if (failed_frame_number == NULL) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
if (hash_error_ != OEMCrypto_SUCCESS)
*failed_frame_number = bad_frame_number_;
return hash_error_;
}
} // namespace wvoec_ref

View File

@@ -73,7 +73,11 @@ class SessionContext {
allowed_schemes_(kSign_RSASSA_PSS),
usage_entry_(NULL),
srm_requirements_status_(NoSRMVersion),
usage_entry_status_(kNoUsageEntry) {}
usage_entry_status_(kNoUsageEntry),
compute_hash_(false),
current_hash_(0),
bad_frame_number_(0),
hash_error_(OEMCrypto_SUCCESS) {}
virtual ~SessionContext();
bool isValid() { return valid_; }
@@ -148,6 +152,11 @@ class SessionContext {
virtual bool QueryKeyControlBlock(const KeyId& key_id, uint32_t* data);
virtual OEMCryptoResult SelectContentKey(const KeyId& key_id,
OEMCryptoCipherMode cipher_mode);
virtual OEMCryptoResult InitializeDecryptHash();
virtual OEMCryptoResult SetDecryptHash(uint32_t frame_number,
const uint8_t* hash,
size_t hash_length);
virtual OEMCryptoResult GetHashErrorCode(uint32_t* failed_frame_number);
const Key* current_content_key(void) { return current_content_key_; }
void set_mac_key_server(const std::vector<uint8_t>& mac_key_server) {
mac_key_server_ = mac_key_server;
@@ -200,6 +209,12 @@ class SessionContext {
OEMCryptoResult CheckStatusOnline(uint32_t nonce, uint32_t control);
// Check that the usage entry status is valid for offline use.
OEMCryptoResult CheckStatusOffline(uint32_t nonce, uint32_t control);
OEMCryptoResult ChooseDecrypt(const uint8_t* iv, size_t block_offset,
const OEMCrypto_CENCEncryptPatternDesc* pattern,
const uint8_t* cipher_data,
size_t cipher_data_length, bool is_encrypted,
uint8_t* clear_data,
OEMCryptoBufferType buffer_type);
OEMCryptoResult DecryptCBC(const uint8_t* key, const uint8_t* iv,
const OEMCrypto_CENCEncryptPatternDesc* pattern,
const uint8_t* cipher_data,
@@ -239,6 +254,13 @@ class SessionContext {
kUsageEntryLoaded, // After loading entry or loading keys.
};
UsageEntryStatus usage_entry_status_;
// These are used when doing full decrypt path testing.
bool compute_hash_; // True if the current frame needs a hash.
uint32_t current_hash_; // Running CRC hash of frame.
uint32_t bad_frame_number_; // Frame number with bad hash.
OEMCryptoResult hash_error_; // Error code for first bad frame.
CORE_DISALLOW_COPY_AND_ASSIGN(SessionContext);
};

View File

@@ -91,6 +91,14 @@ uint32_t wvcrc32(const uint8_t* p_begin, int i_count) {
return(wvrunningcrc32(p_begin, i_count, INIT_CRC32));
}
uint32_t wvcrc32Init() {
return INIT_CRC32;
}
uint32_t wvcrc32Cont(const uint8_t* p_begin, int i_count, uint32_t prev_crc) {
return(wvrunningcrc32(p_begin, i_count, prev_crc));
}
uint32_t wvcrc32n(const uint8_t* p_begin, int i_count) {
return htonl(wvrunningcrc32(p_begin, i_count, INIT_CRC32));
}

View File

@@ -10,6 +10,8 @@
#include <stdint.h>
uint32_t wvcrc32(const uint8_t* p_begin, int i_count);
uint32_t wvcrc32Init();
uint32_t wvcrc32Cont(const uint8_t* p_begin, int i_count, uint32_t prev_crc);
// Convert to network byte order
uint32_t wvcrc32n(const uint8_t* p_begin, int i_count);

View File

@@ -12,10 +12,11 @@ LOCAL_SRC_FILES:= \
oemcrypto_test.cpp \
oemcrypto_test_android.cpp \
oemcrypto_test_main.cpp \
../ref/src/wvcrc.cpp \
LOCAL_C_INCLUDES += \
$(LOCAL_PATH)/../include \
$(LOCAL_PATH)/../mock/src \
$(LOCAL_PATH)/../ref/src \
vendor/widevine/libwvdrmengine/cdm/core/include \
vendor/widevine/libwvdrmengine/cdm/util/include \

View File

@@ -98,6 +98,16 @@ void DeviceFeatures::Initialize(bool is_cast_receiver,
resource_rating = OEMCrypto_ResourceRatingTier();
printf("resource_rating = %d, security leve %s.\n", resource_rating,
OEMCrypto_SecurityLevel());
uint32_t decrypt_hash_type = OEMCrypto_SupportsDecryptHash();
supports_crc = (decrypt_hash_type == OEMCrypto_CRC_Clear_Buffer);
if (supports_crc) {
printf("Decrypt hashes will be tested.\n");
} else {
printf("Decrypt hashes will not be tested -- %s.\n",
decrypt_hash_type == OEMCrypto_Hash_Not_Supported
? "not supported"
: "partner defined hash");
}
switch (derive_key_method) {
case NO_METHOD:
printf("NO_METHOD: Cannot derive known session keys.\n");

View File

@@ -28,6 +28,7 @@ class DeviceFeatures {
bool supports_rsa_3072; // Device supports 3072 bit RSA keys.
bool supports_level_1; // Device supports Level 1 security.
uint32_t resource_rating; // Device's resource rating tier.
bool supports_crc; // Supported decrypt hash type CRC.
uint32_t api_version;
OEMCrypto_ProvisioningMethod provisioning_method;

View File

@@ -36,6 +36,7 @@
#include "oemcrypto_session_tests_helper.h"
#include "oemcrypto_types.h"
#include "string_conversions.h"
#include "wvcrc32.h"
using ::testing::Bool;
using ::testing::Combine;
@@ -1658,7 +1659,7 @@ TEST_P(SessionTestRefreshKeyTest, RefreshWithNoSelectKey) {
ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR(false));
}
// Of only one key control block in the refesh, we update all the keys.
// If only one key control block in the refesh, we update all the keys.
INSTANTIATE_TEST_CASE_P(TestRefreshAllKeys, SessionTestRefreshKeyTest,
Values(std::make_pair(true, 1),
std::make_pair(false, 1)));
@@ -1668,6 +1669,25 @@ INSTANTIATE_TEST_CASE_P(TestRefreshEachKeys, SessionTestRefreshKeyTest,
Values(std::make_pair(true, 4),
std::make_pair(false, 4)));
// If the license does not allow a hash, then we should not compute one.
TEST_F(OEMCryptoSessionTests, HashForbiddenAPI15) {
Session s;
ASSERT_NO_FATAL_FAILURE(s.open());
ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s));
ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(kDuration, 0, 0));
ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign());
ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys());
// Either failure, or not supported is allowed.
ASSERT_NE(OEMCrypto_SUCCESS, OEMCrypto_InitializeDecryptHash(s.session_id()));
ASSERT_EQ(
OEMCrypto_SUCCESS,
OEMCrypto_SelectKey(s.session_id(), s.license().keys[0].key_id,
s.license().keys[0].key_id_length,
OEMCrypto_CipherMode_CTR));
// Still not allowed.
ASSERT_NE(OEMCrypto_SUCCESS, OEMCrypto_InitializeDecryptHash(s.session_id()));
}
//
// Decrypt Tests
//
@@ -1868,7 +1888,8 @@ class OEMCryptoSessionTestsDecryptTests
Session s;
ASSERT_NO_FATAL_FAILURE(s.open());
ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s));
ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(kDuration, 0, 0));
ASSERT_NO_FATAL_FAILURE(
s.FillSimpleMessage(kDuration, kControlAllowHashVerification, 0));
memcpy(s.license().keys[0].key_data, &key[0], key.size());
s.license().keys[0].cipher_mode = cipher_mode_;
ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign());
@@ -1877,7 +1898,10 @@ class OEMCryptoSessionTestsDecryptTests
s.license().keys[0].key_id_length,
cipher_mode_);
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
if (global_features.supports_crc) {
ASSERT_EQ(OEMCrypto_SUCCESS,
OEMCrypto_InitializeDecryptHash(s.session_id()));
}
// We decrypt each subsample.
vector<uint8_t> output_buffer(total_size_ + 16, 0xaa);
const uint8_t *input_buffer = NULL;
@@ -1946,6 +1970,18 @@ class OEMCryptoSessionTestsDecryptTests
EXPECT_EQ(0xaa, output_buffer[total_size_]) << "Buffer overrun.";
output_buffer.resize(total_size_);
EXPECT_EQ(unencryptedData, output_buffer);
if (global_features.supports_crc) {
uint32_t hash =
wvcrc32(&unencryptedData[0], unencryptedData.size());
ASSERT_EQ(OEMCrypto_SUCCESS,
OEMCrypto_SetDecryptHash(
s.session_id(), 1, reinterpret_cast<const uint8_t*>(&hash),
sizeof(hash)));
uint32_t frame;
ASSERT_EQ(OEMCrypto_SUCCESS,
OEMCrypto_GetHashErrorCode(s.session_id(), &frame));
}
}
OEMCrypto_CENCEncryptPatternDesc pattern_;