OLD | NEW |
| (Empty) |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "content/child/webcrypto/crypto_data.h" | |
6 #include "content/child/webcrypto/nss/aes_key_nss.h" | |
7 #include "content/child/webcrypto/nss/key_nss.h" | |
8 #include "content/child/webcrypto/nss/util_nss.h" | |
9 #include "content/child/webcrypto/status.h" | |
10 #include "content/child/webcrypto/webcrypto_util.h" | |
11 #include "third_party/WebKit/public/platform/WebCryptoAlgorithmParams.h" | |
12 | |
13 // At the time of this writing: | |
14 // * Windows and Mac builds ship with their own copy of NSS (3.15+) | |
15 // * Linux builds use the system's libnss, which is 3.14 on Debian (but 3.15+ | |
16 // on other distros). | |
17 // | |
18 // Since NSS provides AES-GCM support starting in version 3.15, it may be | |
19 // unavailable for Linux Chrome users. | |
20 // | |
21 // * !defined(CKM_AES_GCM) | |
22 // | |
23 // This means that at build time, the NSS header pkcs11t.h is older than | |
24 // 3.15. However at runtime support may be present. | |
25 // | |
26 // TODO(eroman): Simplify this once 3.15+ is required by Linux builds. | |
27 #if !defined(CKM_AES_GCM) | |
28 #define CKM_AES_GCM 0x00001087 | |
29 | |
30 struct CK_GCM_PARAMS { | |
31 CK_BYTE_PTR pIv; | |
32 CK_ULONG ulIvLen; | |
33 CK_BYTE_PTR pAAD; | |
34 CK_ULONG ulAADLen; | |
35 CK_ULONG ulTagBits; | |
36 }; | |
37 #endif // !defined(CKM_AES_GCM) | |
38 | |
39 namespace content { | |
40 | |
41 namespace webcrypto { | |
42 | |
43 namespace { | |
44 | |
45 Status NssSupportsAesGcm() { | |
46 if (NssRuntimeSupport::Get()->IsAesGcmSupported()) | |
47 return Status::Success(); | |
48 return Status::ErrorUnsupported( | |
49 "NSS version doesn't support AES-GCM. Try using version 3.15 or later"); | |
50 } | |
51 | |
52 // Helper to either encrypt or decrypt for AES-GCM. The result of encryption is | |
53 // the concatenation of the ciphertext and the authentication tag. Similarly, | |
54 // this is the expectation for the input to decryption. | |
55 Status AesGcmEncryptDecrypt(EncryptOrDecrypt mode, | |
56 const blink::WebCryptoAlgorithm& algorithm, | |
57 const blink::WebCryptoKey& key, | |
58 const CryptoData& data, | |
59 std::vector<uint8>* buffer) { | |
60 Status status = NssSupportsAesGcm(); | |
61 if (status.IsError()) | |
62 return status; | |
63 | |
64 PK11SymKey* sym_key = SymKeyNss::Cast(key)->key(); | |
65 const blink::WebCryptoAesGcmParams* params = algorithm.aesGcmParams(); | |
66 if (!params) | |
67 return Status::ErrorUnexpected(); | |
68 | |
69 unsigned int tag_length_bits; | |
70 status = GetAesGcmTagLengthInBits(params, &tag_length_bits); | |
71 if (status.IsError()) | |
72 return status; | |
73 unsigned int tag_length_bytes = tag_length_bits / 8; | |
74 | |
75 CryptoData iv(params->iv()); | |
76 CryptoData additional_data(params->optionalAdditionalData()); | |
77 | |
78 CK_GCM_PARAMS gcm_params = {0}; | |
79 gcm_params.pIv = const_cast<unsigned char*>(iv.bytes()); | |
80 gcm_params.ulIvLen = iv.byte_length(); | |
81 | |
82 gcm_params.pAAD = const_cast<unsigned char*>(additional_data.bytes()); | |
83 gcm_params.ulAADLen = additional_data.byte_length(); | |
84 | |
85 gcm_params.ulTagBits = tag_length_bits; | |
86 | |
87 SECItem param; | |
88 param.type = siBuffer; | |
89 param.data = reinterpret_cast<unsigned char*>(&gcm_params); | |
90 param.len = sizeof(gcm_params); | |
91 | |
92 unsigned int buffer_size = 0; | |
93 | |
94 // Calculate the output buffer size. | |
95 if (mode == ENCRYPT) { | |
96 // TODO(eroman): This is ugly, abstract away the safe integer arithmetic. | |
97 if (data.byte_length() > (UINT_MAX - tag_length_bytes)) | |
98 return Status::ErrorDataTooLarge(); | |
99 buffer_size = data.byte_length() + tag_length_bytes; | |
100 } else { | |
101 // TODO(eroman): In theory the buffer allocated for the plain text should be | |
102 // sized as |data.byte_length() - tag_length_bytes|. | |
103 // | |
104 // However NSS has a bug whereby it will fail if the output buffer size is | |
105 // not at least as large as the ciphertext: | |
106 // | |
107 // https://bugzilla.mozilla.org/show_bug.cgi?id=%20853674 | |
108 // | |
109 // From the analysis of that bug it looks like it might be safe to pass a | |
110 // correctly sized buffer but lie about its size. Since resizing the | |
111 // WebCryptoArrayBuffer is expensive that hack may be worth looking into. | |
112 buffer_size = data.byte_length(); | |
113 } | |
114 | |
115 buffer->resize(buffer_size); | |
116 unsigned char* buffer_data = Uint8VectorStart(buffer); | |
117 | |
118 PK11_EncryptDecryptFunction encrypt_or_decrypt_func = | |
119 (mode == ENCRYPT) ? NssRuntimeSupport::Get()->pk11_encrypt_func() | |
120 : NssRuntimeSupport::Get()->pk11_decrypt_func(); | |
121 | |
122 unsigned int output_len = 0; | |
123 SECStatus result = encrypt_or_decrypt_func(sym_key, | |
124 CKM_AES_GCM, | |
125 ¶m, | |
126 buffer_data, | |
127 &output_len, | |
128 buffer->size(), | |
129 data.bytes(), | |
130 data.byte_length()); | |
131 | |
132 if (result != SECSuccess) | |
133 return Status::OperationError(); | |
134 | |
135 // Unfortunately the buffer needs to be shrunk for decryption (see the NSS bug | |
136 // above). | |
137 buffer->resize(output_len); | |
138 | |
139 return Status::Success(); | |
140 } | |
141 | |
142 class AesGcmImplementation : public AesAlgorithm { | |
143 public: | |
144 AesGcmImplementation() : AesAlgorithm(CKM_AES_GCM, "GCM") {} | |
145 | |
146 virtual Status VerifyKeyUsagesBeforeImportKey( | |
147 blink::WebCryptoKeyFormat format, | |
148 blink::WebCryptoKeyUsageMask usage_mask) const OVERRIDE { | |
149 // Prevent importing AES-GCM keys if it is unavailable. | |
150 Status status = NssSupportsAesGcm(); | |
151 if (status.IsError()) | |
152 return status; | |
153 return AesAlgorithm::VerifyKeyUsagesBeforeImportKey(format, usage_mask); | |
154 } | |
155 | |
156 virtual Status VerifyKeyUsagesBeforeGenerateKey( | |
157 blink::WebCryptoKeyUsageMask usage_mask) const OVERRIDE { | |
158 // Prevent generating AES-GCM keys if it is unavailable. | |
159 Status status = NssSupportsAesGcm(); | |
160 if (status.IsError()) | |
161 return status; | |
162 return AesAlgorithm::VerifyKeyUsagesBeforeGenerateKey(usage_mask); | |
163 } | |
164 | |
165 virtual Status Encrypt(const blink::WebCryptoAlgorithm& algorithm, | |
166 const blink::WebCryptoKey& key, | |
167 const CryptoData& data, | |
168 std::vector<uint8>* buffer) const OVERRIDE { | |
169 return AesGcmEncryptDecrypt(ENCRYPT, algorithm, key, data, buffer); | |
170 } | |
171 | |
172 virtual Status Decrypt(const blink::WebCryptoAlgorithm& algorithm, | |
173 const blink::WebCryptoKey& key, | |
174 const CryptoData& data, | |
175 std::vector<uint8>* buffer) const OVERRIDE { | |
176 return AesGcmEncryptDecrypt(DECRYPT, algorithm, key, data, buffer); | |
177 } | |
178 }; | |
179 | |
180 } // namespace | |
181 | |
182 AlgorithmImplementation* CreatePlatformAesGcmImplementation() { | |
183 return new AesGcmImplementation; | |
184 } | |
185 | |
186 } // namespace webcrypto | |
187 | |
188 } // namespace content | |
OLD | NEW |