summaryrefslogtreecommitdiff
path: root/chromium/content/child/webcrypto/nss/aes_gcm_nss.cc
blob: 242fdfd48c46195e6abef6f6eec989e5a79899fd (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
// Copyright 2014 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 "base/numerics/safe_math.h"
#include "base/stl_util.h"
#include "content/child/webcrypto/crypto_data.h"
#include "content/child/webcrypto/nss/aes_key_nss.h"
#include "content/child/webcrypto/nss/key_nss.h"
#include "content/child/webcrypto/nss/util_nss.h"
#include "content/child/webcrypto/status.h"
#include "content/child/webcrypto/webcrypto_util.h"
#include "third_party/WebKit/public/platform/WebCryptoAlgorithmParams.h"

// At the time of this writing:
//   * Windows and Mac builds ship with their own copy of NSS (3.15+)
//   * Linux builds use the system's libnss, which is 3.14 on Debian (but 3.15+
//     on other distros).
//
// Since NSS provides AES-GCM support starting in version 3.15, it may be
// unavailable for Linux Chrome users.
//
//  * !defined(CKM_AES_GCM)
//
//      This means that at build time, the NSS header pkcs11t.h is older than
//      3.15. However at runtime support may be present.
//
// TODO(eroman): Simplify this once 3.15+ is required by Linux builds.
#if !defined(CKM_AES_GCM)
#define CKM_AES_GCM 0x00001087

struct CK_GCM_PARAMS {
  CK_BYTE_PTR pIv;
  CK_ULONG ulIvLen;
  CK_BYTE_PTR pAAD;
  CK_ULONG ulAADLen;
  CK_ULONG ulTagBits;
};
#endif  // !defined(CKM_AES_GCM)

namespace content {

namespace webcrypto {

namespace {

Status NssSupportsAesGcm() {
  if (NssRuntimeSupport::Get()->IsAesGcmSupported())
    return Status::Success();
  return Status::ErrorUnsupported(
      "NSS version doesn't support AES-GCM. Try using version 3.15 or later");
}

// Helper to either encrypt or decrypt for AES-GCM. The result of encryption is
// the concatenation of the ciphertext and the authentication tag. Similarly,
// this is the expectation for the input to decryption.
Status AesGcmEncryptDecrypt(EncryptOrDecrypt mode,
                            const blink::WebCryptoAlgorithm& algorithm,
                            const blink::WebCryptoKey& key,
                            const CryptoData& data,
                            std::vector<uint8_t>* buffer) {
  Status status = NssSupportsAesGcm();
  if (status.IsError())
    return status;

  PK11SymKey* sym_key = SymKeyNss::Cast(key)->key();
  const blink::WebCryptoAesGcmParams* params = algorithm.aesGcmParams();
  if (!params)
    return Status::ErrorUnexpected();

  unsigned int tag_length_bits;
  status = GetAesGcmTagLengthInBits(params, &tag_length_bits);
  if (status.IsError())
    return status;
  unsigned int tag_length_bytes = tag_length_bits / 8;

  CryptoData iv(params->iv());
  CryptoData additional_data(params->optionalAdditionalData());

  CK_GCM_PARAMS gcm_params = {0};
  gcm_params.pIv = const_cast<unsigned char*>(iv.bytes());
  gcm_params.ulIvLen = iv.byte_length();

  gcm_params.pAAD = const_cast<unsigned char*>(additional_data.bytes());
  gcm_params.ulAADLen = additional_data.byte_length();

  gcm_params.ulTagBits = tag_length_bits;

  SECItem param;
  param.type = siBuffer;
  param.data = reinterpret_cast<unsigned char*>(&gcm_params);
  param.len = sizeof(gcm_params);

  base::CheckedNumeric<unsigned int> buffer_size(data.byte_length());

  // Calculate the output buffer size.
  if (mode == ENCRYPT) {
    buffer_size += tag_length_bytes;
    if (!buffer_size.IsValid())
      return Status::ErrorDataTooLarge();
  }

  // TODO(eroman): In theory the buffer allocated for the plain text should be
  // sized as |data.byte_length() - tag_length_bytes|.
  //
  // However NSS has a bug whereby it will fail if the output buffer size is
  // not at least as large as the ciphertext:
  //
  // https://bugzilla.mozilla.org/show_bug.cgi?id=%20853674
  //
  // From the analysis of that bug it looks like it might be safe to pass a
  // correctly sized buffer but lie about its size. Since resizing the
  // WebCryptoArrayBuffer is expensive that hack may be worth looking into.

  buffer->resize(buffer_size.ValueOrDie());
  unsigned char* buffer_data = vector_as_array(buffer);

  PK11_EncryptDecryptFunction encrypt_or_decrypt_func =
      (mode == ENCRYPT) ? NssRuntimeSupport::Get()->pk11_encrypt_func()
                        : NssRuntimeSupport::Get()->pk11_decrypt_func();

  unsigned int output_len = 0;
  SECStatus result = encrypt_or_decrypt_func(sym_key,
                                             CKM_AES_GCM,
                                             &param,
                                             buffer_data,
                                             &output_len,
                                             buffer->size(),
                                             data.bytes(),
                                             data.byte_length());

  if (result != SECSuccess)
    return Status::OperationError();

  // Unfortunately the buffer needs to be shrunk for decryption (see the NSS bug
  // above).
  buffer->resize(output_len);

  return Status::Success();
}

class AesGcmImplementation : public AesAlgorithm {
 public:
  AesGcmImplementation() : AesAlgorithm(CKM_AES_GCM, "GCM") {}

  Status VerifyKeyUsagesBeforeImportKey(
      blink::WebCryptoKeyFormat format,
      blink::WebCryptoKeyUsageMask usages) const override {
    // Prevent importing AES-GCM keys if it is unavailable.
    Status status = NssSupportsAesGcm();
    if (status.IsError())
      return status;
    return AesAlgorithm::VerifyKeyUsagesBeforeImportKey(format, usages);
  }

  Status GenerateKey(const blink::WebCryptoAlgorithm& algorithm,
                     bool extractable,
                     blink::WebCryptoKeyUsageMask usages,
                     GenerateKeyResult* result) const override {
    // Prevent generating AES-GCM keys if it is unavailable.
    Status status = NssSupportsAesGcm();
    if (status.IsError())
      return status;

    return AesAlgorithm::GenerateKey(algorithm, extractable, usages, result);
  }

  Status Encrypt(const blink::WebCryptoAlgorithm& algorithm,
                 const blink::WebCryptoKey& key,
                 const CryptoData& data,
                 std::vector<uint8_t>* buffer) const override {
    return AesGcmEncryptDecrypt(ENCRYPT, algorithm, key, data, buffer);
  }

  Status Decrypt(const blink::WebCryptoAlgorithm& algorithm,
                 const blink::WebCryptoKey& key,
                 const CryptoData& data,
                 std::vector<uint8_t>* buffer) const override {
    return AesGcmEncryptDecrypt(DECRYPT, algorithm, key, data, buffer);
  }
};

}  // namespace

AlgorithmImplementation* CreatePlatformAesGcmImplementation() {
  return new AesGcmImplementation;
}

}  // namespace webcrypto

}  // namespace content