summaryrefslogtreecommitdiff
path: root/chromium/media/cdm/aes_cbc_crypto_unittest.cc
blob: ea3ea46af522c191670f41347e456f7df3840c30 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/cdm/aes_cbc_crypto.h"

#include <memory>

#include "base/containers/span.h"
#include "crypto/encryptor.h"
#include "crypto/symmetric_key.h"
#include "media/base/decoder_buffer.h"
#include "media/base/decrypt_config.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/boringssl/src/include/openssl/aes.h"
#include "third_party/boringssl/src/include/openssl/crypto.h"
#include "third_party/boringssl/src/include/openssl/err.h"
#include "third_party/boringssl/src/include/openssl/evp.h"

namespace media {

namespace {

// Pattern decryption uses 16-byte blocks.
constexpr size_t kBlockSize = 16;

// Keys and IV have to be 128 bits.
const uint8_t kKey1[] = {0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
                         0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13};
static_assert(std::size(kKey1) == 128 / 8, "kKey1 must be 128 bits");

const uint8_t kKey2[] = {0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13,
                         0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b};
static_assert(std::size(kKey2) == 128 / 8, "kKey2 must be 128 bits");

const uint8_t kIv[] = {0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
                       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
static_assert(std::size(kIv) == 128 / 8, "kIv must be 128 bits");

const uint8_t kOneBlock[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
                             'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p'};
static_assert(std::size(kOneBlock) == kBlockSize, "kOneBlock not block sized");

std::string MakeString(const std::vector<uint8_t>& chars) {
  return std::string(chars.begin(), chars.end());
}

// Returns a std::vector<uint8_t> containing |count| copies of |input|.
std::vector<uint8_t> Repeat(const std::vector<uint8_t>& input, size_t count) {
  std::vector<uint8_t> result;
  for (size_t i = 0; i < count; ++i)
    result.insert(result.end(), input.begin(), input.end());
  return result;
}

}  // namespace

class AesCbcCryptoTest : public testing::Test {
 public:
  AesCbcCryptoTest()
      : key1_(crypto::SymmetricKey::Import(
            crypto::SymmetricKey::AES,
            std::string(std::begin(kKey1), std::end(kKey1)))),
        key2_(crypto::SymmetricKey::Import(
            crypto::SymmetricKey::AES,
            std::string(std::begin(kKey2), std::end(kKey2)))),
        iv_(std::begin(kIv), std::end(kIv)),
        one_block_(std::begin(kOneBlock), std::end(kOneBlock)) {}

  // Encrypt |original| using AES-CBC encryption with |key| and |iv|.
  std::vector<uint8_t> Encrypt(const std::vector<uint8_t>& original,
                               const crypto::SymmetricKey& key,
                               base::span<const uint8_t> iv) {
    // This code uses crypto::Encryptor to encrypt |original| rather than
    // calling EVP_EncryptInit_ex() / EVP_EncryptUpdate() / etc. This is done
    // for simplicity, as the crypto:: code wraps all the calls up nicely.
    // However, for AES-CBC encryption, the crypto:: code does add padding to
    // the output, which is simply stripped off.
    crypto::Encryptor encryptor;
    std::string iv_as_string(std::begin(iv), std::end(iv));
    EXPECT_TRUE(encryptor.Init(&key, crypto::Encryptor::CBC, iv_as_string));

    std::string ciphertext;
    EXPECT_TRUE(encryptor.Encrypt(MakeString(original), &ciphertext));

    // CBC encyption adds a block of padding at the end, so discard it.
    EXPECT_GT(ciphertext.size(), original.size());
    ciphertext.resize(original.size());

    return std::vector<uint8_t>(ciphertext.begin(), ciphertext.end());
  }

  // Constants for testing.
  std::unique_ptr<crypto::SymmetricKey> key1_;
  std::unique_ptr<crypto::SymmetricKey> key2_;
  base::span<const uint8_t> iv_;
  const std::vector<uint8_t> one_block_;
};

TEST_F(AesCbcCryptoTest, OneBlock) {
  auto encrypted_block = Encrypt(one_block_, *key1_, iv_);
  EXPECT_EQ(kBlockSize, encrypted_block.size());

  AesCbcCrypto crypto;
  EXPECT_TRUE(crypto.Initialize(*key1_, iv_));

  std::vector<uint8_t> output(encrypted_block.size());
  EXPECT_TRUE(crypto.Decrypt(encrypted_block, output.data()));
  EXPECT_EQ(output, one_block_);
}

TEST_F(AesCbcCryptoTest, WrongKey) {
  auto encrypted_block = Encrypt(one_block_, *key1_, iv_);
  EXPECT_EQ(kBlockSize, encrypted_block.size());

  // Use |key2_| when trying to decrypt.
  AesCbcCrypto crypto;
  EXPECT_TRUE(crypto.Initialize(*key2_, iv_));

  std::vector<uint8_t> output(encrypted_block.size());
  EXPECT_TRUE(crypto.Decrypt(encrypted_block, output.data()));
  EXPECT_NE(output, one_block_);
}

TEST_F(AesCbcCryptoTest, WrongIV) {
  auto encrypted_block = Encrypt(one_block_, *key1_, iv_);
  EXPECT_EQ(kBlockSize, encrypted_block.size());

  // Use a different IV when trying to decrypt.
  AesCbcCrypto crypto;
  std::vector<uint8_t> alternate_iv(iv_.size(), 'a');
  EXPECT_TRUE(crypto.Initialize(*key1_, alternate_iv));

  std::vector<uint8_t> output(encrypted_block.size());
  EXPECT_TRUE(crypto.Decrypt(encrypted_block, output.data()));
  EXPECT_NE(output, one_block_);
}

TEST_F(AesCbcCryptoTest, PartialBlock) {
  auto encrypted_block = Encrypt(one_block_, *key1_, iv_);
  EXPECT_EQ(kBlockSize, encrypted_block.size());

  AesCbcCrypto crypto;
  EXPECT_TRUE(crypto.Initialize(*key2_, iv_));

  // Try to decrypt less than a full block.
  std::vector<uint8_t> output(encrypted_block.size());
  EXPECT_FALSE(crypto.Decrypt(
      base::make_span(encrypted_block).first(encrypted_block.size() - 5),
      output.data()));
}

TEST_F(AesCbcCryptoTest, MultipleBlocks) {
  // Encrypt 10 copies of |one_block_| together.
  constexpr size_t kNumBlocksInData = 10;
  auto encrypted_block =
      Encrypt(Repeat(one_block_, kNumBlocksInData), *key2_, iv_);
  ASSERT_EQ(kNumBlocksInData * kBlockSize, encrypted_block.size());

  AesCbcCrypto crypto;
  EXPECT_TRUE(crypto.Initialize(*key2_, iv_));

  std::vector<uint8_t> output(encrypted_block.size());
  EXPECT_TRUE(crypto.Decrypt(encrypted_block, output.data()));
  EXPECT_EQ(output, Repeat(one_block_, kNumBlocksInData));
}

// As the code in aes_cbc_crypto.cc relies on decrypting the data block by
// block, ensure that the crypto routines work the same way whether it
// decrypts one block at a time or all the blocks in one call.
TEST_F(AesCbcCryptoTest, BlockDecryptionWorks) {
  constexpr size_t kNumBlocksInData = 5;
  std::vector<uint8_t> data = {1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2,
                               3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4,
                               5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6,
                               7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8,
                               9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0};
  ASSERT_EQ(data.size(), kNumBlocksInData * kBlockSize);
  auto encrypted_data = Encrypt(data, *key1_, iv_);

  // Decrypt |encrypted_data| in one pass.
  {
    AesCbcCrypto crypto;
    EXPECT_TRUE(crypto.Initialize(*key1_, iv_));

    std::vector<uint8_t> output(kNumBlocksInData * kBlockSize);
    EXPECT_TRUE(crypto.Decrypt(encrypted_data, output.data()));
    EXPECT_EQ(output, data);
  }

  // Repeat but call Decrypt() once for each block.
  {
    AesCbcCrypto crypto;
    EXPECT_TRUE(crypto.Initialize(*key1_, iv_));

    std::vector<uint8_t> output(kNumBlocksInData * kBlockSize);
    auto input = base::make_span(encrypted_data);
    for (size_t offset = 0; offset < output.size(); offset += kBlockSize) {
      EXPECT_TRUE(
          crypto.Decrypt(input.subspan(offset, kBlockSize), &output[offset]));
    }
    EXPECT_EQ(output, data);
  }
}

}  // namespace media