]> git.saurik.com Git - apple/securityd.git/blob - src/dbcrypto.cpp
securityd-32661.tar.gz
[apple/securityd.git] / src / dbcrypto.cpp
1 /*
2 * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24
25 //
26 // dbcrypto - cryptographic core for database and key blob cryptography
27 //
28 #include "dbcrypto.h"
29 #include <securityd_client/ssblob.h>
30 #include "server.h" // just for Server::csp()
31 #include <security_cdsa_client/genkey.h>
32 #include <security_cdsa_client/cryptoclient.h>
33 #include <security_cdsa_client/keyclient.h>
34 #include <security_cdsa_client/macclient.h>
35 #include <security_cdsa_client/wrapkey.h>
36 #include <security_cdsa_utilities/cssmendian.h>
37
38 using namespace CssmClient;
39 using LowLevelMemoryUtilities::fieldOffsetOf;
40
41
42 //
43 // The CryptoCore constructor doesn't do anything interesting.
44 // It just initializes us to "empty".
45 //
46 DatabaseCryptoCore::DatabaseCryptoCore() : mHaveMaster(false), mIsValid(false)
47 {
48 }
49
50 DatabaseCryptoCore::~DatabaseCryptoCore()
51 {
52 // key objects take care of themselves
53 }
54
55
56 //
57 // Forget the secrets
58 //
59 void DatabaseCryptoCore::invalidate()
60 {
61 mMasterKey.release();
62 mHaveMaster = false;
63
64 mEncryptionKey.release();
65 mSigningKey.release();
66 mIsValid = false;
67 }
68
69
70 //
71 // Generate new secrets for this crypto core.
72 //
73 void DatabaseCryptoCore::generateNewSecrets()
74 {
75 // create a random DES3 key
76 GenerateKey desGenerator(Server::csp(), CSSM_ALGID_3DES_3KEY_EDE, 24 * 8);
77 mEncryptionKey = desGenerator(KeySpec(CSSM_KEYUSE_WRAP | CSSM_KEYUSE_UNWRAP,
78 CSSM_KEYATTR_RETURN_DATA | CSSM_KEYATTR_EXTRACTABLE));
79
80 // create a random 20 byte HMAC/SHA1 signing "key"
81 GenerateKey signGenerator(Server::csp(), CSSM_ALGID_SHA1HMAC,
82 sizeof(DbBlob::PrivateBlob::SigningKey) * 8);
83 mSigningKey = signGenerator(KeySpec(CSSM_KEYUSE_SIGN | CSSM_KEYUSE_VERIFY,
84 CSSM_KEYATTR_RETURN_DATA | CSSM_KEYATTR_EXTRACTABLE));
85
86 // secrets established
87 mIsValid = true;
88 }
89
90
91 CssmClient::Key DatabaseCryptoCore::masterKey()
92 {
93 assert(mHaveMaster);
94 return mMasterKey;
95 }
96
97
98 //
99 // Establish the master secret as derived from a passphrase passed in.
100 // If a DbBlob is passed, take the salt from it and remember it.
101 // If a NULL DbBlob is passed, generate a new (random) salt.
102 // Note that the passphrase is NOT remembered; only the master key.
103 //
104 void DatabaseCryptoCore::setup(const DbBlob *blob, const CssmData &passphrase)
105 {
106 if (blob)
107 memcpy(mSalt, blob->salt, sizeof(mSalt));
108 else
109 Server::active().random(mSalt);
110 mMasterKey = deriveDbMasterKey(passphrase);
111 mHaveMaster = true;
112 }
113
114
115 //
116 // Establish the master secret directly from a master key passed in.
117 // We will copy the KeyData (caller still owns its copy).
118 // Blob/salt handling as above.
119 //
120 void DatabaseCryptoCore::setup(const DbBlob *blob, CssmClient::Key master)
121 {
122 // pre-screen the key
123 CssmKey::Header header = master.header();
124 if (header.keyClass() != CSSM_KEYCLASS_SESSION_KEY)
125 CssmError::throwMe(CSSMERR_CSP_INVALID_KEY_CLASS);
126 if (header.algorithm() != CSSM_ALGID_3DES_3KEY_EDE)
127 CssmError::throwMe(CSSMERR_CSP_INVALID_ALGORITHM);
128
129 // accept it
130 if (blob)
131 memcpy(mSalt, blob->salt, sizeof(mSalt));
132 else
133 Server::active().random(mSalt);
134 mMasterKey = master;
135 mHaveMaster = true;
136 }
137
138
139 //
140 // Given a putative passphrase, determine whether that passphrase
141 // properly generates the database's master secret.
142 // Return a boolean accordingly. Do not change our state.
143 // The database must have a master secret (to compare with).
144 // Note that any errors thrown by the cryptography here will actually
145 // throw out of validatePassphrase, since they "should not happen" and
146 // thus indicate a problem *beyond* (just) a bad passphrase.
147 //
148 bool DatabaseCryptoCore::validatePassphrase(const CssmData &passphrase)
149 {
150 assert(hasMaster());
151 CssmClient::Key master = deriveDbMasterKey(passphrase);
152
153 // to compare master with mMaster, see if they encrypt alike
154 StringData probe
155 ("Now is the time for all good processes to come to the aid of their kernel.");
156 CssmData noRemainder((void *)1, 0); // no cipher overflow
157 Encrypt cryptor(Server::csp(), CSSM_ALGID_3DES_3KEY_EDE);
158 cryptor.mode(CSSM_ALGMODE_CBCPadIV8);
159 cryptor.padding(CSSM_PADDING_PKCS1);
160 uint8 iv[8]; // leave uninitialized; pseudo-random is cool
161 cryptor.initVector(CssmData::wrap(iv));
162
163 cryptor.key(master);
164 CssmAutoData cipher1(Server::csp().allocator());
165 cryptor.encrypt(probe, cipher1.get(), noRemainder);
166
167 cryptor.key(mMasterKey);
168 CssmAutoData cipher2(Server::csp().allocator());
169 cryptor.encrypt(probe, cipher2.get(), noRemainder);
170
171 return cipher1 == cipher2;
172 }
173
174
175 //
176 // Encode a database blob from the core.
177 //
178 DbBlob *DatabaseCryptoCore::encodeCore(const DbBlob &blobTemplate,
179 const CssmData &publicAcl, const CssmData &privateAcl) const
180 {
181 assert(isValid()); // must have secrets to work from
182
183 // make a new IV
184 uint8 iv[8];
185 Server::active().random(iv);
186
187 // build the encrypted section blob
188 CssmData &encryptionBits = *mEncryptionKey;
189 CssmData &signingBits = *mSigningKey;
190 CssmData incrypt[3];
191 incrypt[0] = encryptionBits;
192 incrypt[1] = signingBits;
193 incrypt[2] = privateAcl;
194 CssmData cryptoBlob, remData;
195 Encrypt cryptor(Server::csp(), CSSM_ALGID_3DES_3KEY_EDE);
196 cryptor.mode(CSSM_ALGMODE_CBCPadIV8);
197 cryptor.padding(CSSM_PADDING_PKCS1);
198 cryptor.key(mMasterKey);
199 CssmData ivd(iv, sizeof(iv)); cryptor.initVector(ivd);
200 cryptor.encrypt(incrypt, 3, &cryptoBlob, 1, remData);
201
202 // allocate the final DbBlob, uh, blob
203 size_t length = sizeof(DbBlob) + publicAcl.length() + cryptoBlob.length();
204 DbBlob *blob = Allocator::standard().malloc<DbBlob>(length);
205
206 // assemble the DbBlob
207 memset(blob, 0x7d, sizeof(DbBlob)); // deterministically fill any alignment gaps
208 blob->initialize();
209 blob->randomSignature = blobTemplate.randomSignature;
210 blob->sequence = blobTemplate.sequence;
211 blob->params = blobTemplate.params;
212 memcpy(blob->salt, mSalt, sizeof(blob->salt));
213 memcpy(blob->iv, iv, sizeof(iv));
214 memcpy(blob->publicAclBlob(), publicAcl, publicAcl.length());
215 blob->startCryptoBlob = sizeof(DbBlob) + publicAcl.length();
216 memcpy(blob->cryptoBlob(), cryptoBlob, cryptoBlob.length());
217 blob->totalLength = blob->startCryptoBlob + cryptoBlob.length();
218
219 // sign the blob
220 CssmData signChunk[] = {
221 CssmData(blob->data(), fieldOffsetOf(&DbBlob::blobSignature)),
222 CssmData(blob->publicAclBlob(), publicAcl.length() + cryptoBlob.length())
223 };
224 CssmData signature(blob->blobSignature, sizeof(blob->blobSignature));
225 GenerateMac signer(Server::csp(), CSSM_ALGID_SHA1HMAC_LEGACY);
226 signer.key(mSigningKey);
227 signer.sign(signChunk, 2, signature);
228 assert(signature.length() == sizeof(blob->blobSignature));
229
230 // all done. Clean up
231 Server::csp()->allocator().free(cryptoBlob);
232 return blob;
233 }
234
235
236 //
237 // Decode a database blob into the core.
238 // Throws exceptions if decoding fails.
239 // Memory returned in privateAclBlob is allocated and becomes owned by caller.
240 //
241 void DatabaseCryptoCore::decodeCore(const DbBlob *blob, void **privateAclBlob)
242 {
243 assert(mHaveMaster); // must have master key installed
244
245 // try to decrypt the cryptoblob section
246 Decrypt decryptor(Server::csp(), CSSM_ALGID_3DES_3KEY_EDE);
247 decryptor.mode(CSSM_ALGMODE_CBCPadIV8);
248 decryptor.padding(CSSM_PADDING_PKCS1);
249 decryptor.key(mMasterKey);
250 CssmData ivd = CssmData::wrap(blob->iv); decryptor.initVector(ivd);
251 CssmData cryptoBlob = CssmData::wrap(blob->cryptoBlob(), blob->cryptoBlobLength());
252 CssmData decryptedBlob, remData;
253 decryptor.decrypt(cryptoBlob, decryptedBlob, remData);
254 DbBlob::PrivateBlob *privateBlob = decryptedBlob.interpretedAs<DbBlob::PrivateBlob>();
255
256 // tentatively establish keys
257 mEncryptionKey = makeRawKey(privateBlob->encryptionKey,
258 sizeof(privateBlob->encryptionKey), CSSM_ALGID_3DES_3KEY_EDE,
259 CSSM_KEYUSE_WRAP | CSSM_KEYUSE_UNWRAP);
260 mSigningKey = makeRawKey(privateBlob->signingKey,
261 sizeof(privateBlob->signingKey), CSSM_ALGID_SHA1HMAC,
262 CSSM_KEYUSE_SIGN | CSSM_KEYUSE_VERIFY);
263
264 // verify signature on the whole blob
265 CssmData signChunk[] = {
266 CssmData::wrap(blob->data(), fieldOffsetOf(&DbBlob::blobSignature)),
267 CssmData::wrap(blob->publicAclBlob(), blob->publicAclBlobLength() + blob->cryptoBlobLength())
268 };
269 CSSM_ALGORITHMS verifyAlgorithm = CSSM_ALGID_SHA1HMAC;
270 #if defined(COMPAT_OSX_10_0)
271 if (blob->version() == blob->version_MacOS_10_0)
272 verifyAlgorithm = CSSM_ALGID_SHA1HMAC_LEGACY; // BSafe bug compatibility
273 #endif
274 VerifyMac verifier(Server::csp(), verifyAlgorithm);
275 verifier.key(mSigningKey);
276 verifier.verify(signChunk, 2, CssmData::wrap(blob->blobSignature));
277
278 // all checks out; start extracting fields
279 if (privateAclBlob) {
280 // extract private ACL blob as a separately allocated area
281 uint32 blobLength = decryptedBlob.length() - sizeof(DbBlob::PrivateBlob);
282 *privateAclBlob = Allocator::standard().malloc(blobLength);
283 memcpy(*privateAclBlob, privateBlob->privateAclBlob(), blobLength);
284 }
285
286 // secrets have been established
287 mIsValid = true;
288 Allocator::standard().free(privateBlob);
289 }
290
291
292 //
293 // Make another DatabaseCryptoCore's operational secrets our own.
294 // Intended for keychain synchronization.
295 //
296 void DatabaseCryptoCore::importSecrets(const DatabaseCryptoCore &src)
297 {
298 assert(src.isValid()); // must have called src.decodeCore() first
299 assert(hasMaster());
300 mEncryptionKey = src.mEncryptionKey;
301 mSigningKey = src.mSigningKey;
302 mIsValid = true;
303 }
304
305 //
306 // Encode a key blob
307 //
308 KeyBlob *DatabaseCryptoCore::encodeKeyCore(const CssmKey &inKey,
309 const CssmData &publicAcl, const CssmData &privateAcl,
310 bool inTheClear) const
311 {
312 CssmKey key = inKey;
313 uint8 iv[8];
314 CssmKey wrappedKey;
315
316 if(inTheClear && (privateAcl.Length != 0)) {
317 /* can't store private ACL component in the clear */
318 CssmError::throwMe(CSSMERR_DL_INVALID_ACCESS_CREDENTIALS);
319 }
320
321 // extract and hold some header bits the CSP does not want to see
322 uint32 heldAttributes = key.attributes() & managedAttributes;
323 key.clearAttribute(managedAttributes);
324 key.setAttribute(forcedAttributes);
325
326 if(inTheClear) {
327 /* NULL wrap of public key */
328 WrapKey wrap(Server::csp(), CSSM_ALGID_NONE);
329 wrap(key, wrappedKey, NULL);
330 }
331 else {
332 assert(isValid()); // need our database secrets
333
334 // create new IV
335 Server::active().random(iv);
336
337 // use a CMS wrap to encrypt the key
338 WrapKey wrap(Server::csp(), CSSM_ALGID_3DES_3KEY_EDE);
339 wrap.key(mEncryptionKey);
340 wrap.mode(CSSM_ALGMODE_CBCPadIV8);
341 wrap.padding(CSSM_PADDING_PKCS1);
342 CssmData ivd(iv, sizeof(iv)); wrap.initVector(ivd);
343 wrap.add(CSSM_ATTRIBUTE_WRAPPED_KEY_FORMAT,
344 uint32(CSSM_KEYBLOB_WRAPPED_FORMAT_APPLE_CUSTOM));
345 wrap(key, wrappedKey, &privateAcl);
346 }
347
348 // stick the held attribute bits back in
349 key.clearAttribute(forcedAttributes);
350 key.setAttribute(heldAttributes);
351
352 // allocate the final KeyBlob, uh, blob
353 size_t length = sizeof(KeyBlob) + publicAcl.length() + wrappedKey.length();
354 KeyBlob *blob = Allocator::standard().malloc<KeyBlob>(length);
355
356 // assemble the KeyBlob
357 memset(blob, 0, sizeof(KeyBlob)); // fill alignment gaps
358 blob->initialize();
359 if(!inTheClear) {
360 memcpy(blob->iv, iv, sizeof(iv));
361 }
362 blob->header = key.header();
363 h2ni(blob->header); // endian-correct the header
364 blob->wrappedHeader.blobType = wrappedKey.blobType();
365 blob->wrappedHeader.blobFormat = wrappedKey.blobFormat();
366 blob->wrappedHeader.wrapAlgorithm = wrappedKey.wrapAlgorithm();
367 blob->wrappedHeader.wrapMode = wrappedKey.wrapMode();
368 memcpy(blob->publicAclBlob(), publicAcl, publicAcl.length());
369 blob->startCryptoBlob = sizeof(KeyBlob) + publicAcl.length();
370 memcpy(blob->cryptoBlob(), wrappedKey.data(), wrappedKey.length());
371 blob->totalLength = blob->startCryptoBlob + wrappedKey.length();
372
373 if(inTheClear) {
374 /* indicate that this is cleartext for decoding */
375 blob->setClearTextSignature();
376 }
377 else {
378 // sign the blob
379 CssmData signChunk[] = {
380 CssmData(blob->data(), fieldOffsetOf(&KeyBlob::blobSignature)),
381 CssmData(blob->publicAclBlob(), blob->publicAclBlobLength() + blob->cryptoBlobLength())
382 };
383 CssmData signature(blob->blobSignature, sizeof(blob->blobSignature));
384 GenerateMac signer(Server::csp(), CSSM_ALGID_SHA1HMAC_LEGACY); //@@@!!! CRUD
385 signer.key(mSigningKey);
386 signer.sign(signChunk, 2, signature);
387 assert(signature.length() == sizeof(blob->blobSignature));
388 }
389
390 // all done. Clean up
391 Server::csp()->allocator().free(wrappedKey);
392 return blob;
393 }
394
395
396 //
397 // Decode a key blob
398 //
399 void DatabaseCryptoCore::decodeKeyCore(KeyBlob *blob,
400 CssmKey &key, void * &pubAcl, void * &privAcl) const
401 {
402 // Assemble the encrypted blob as a CSSM "wrapped key"
403 CssmKey wrappedKey;
404 wrappedKey.KeyHeader = blob->header;
405 h2ni(wrappedKey.KeyHeader);
406 wrappedKey.blobType(blob->wrappedHeader.blobType);
407 wrappedKey.blobFormat(blob->wrappedHeader.blobFormat);
408 wrappedKey.wrapAlgorithm(blob->wrappedHeader.wrapAlgorithm);
409 wrappedKey.wrapMode(blob->wrappedHeader.wrapMode);
410 wrappedKey.KeyData = CssmData(blob->cryptoBlob(), blob->cryptoBlobLength());
411
412 bool inTheClear = blob->isClearText();
413 if(!inTheClear) {
414 // verify signature (check against corruption)
415 assert(isValid()); // need our database secrets
416 CssmData signChunk[] = {
417 CssmData::wrap(blob, fieldOffsetOf(&KeyBlob::blobSignature)),
418 CssmData(blob->publicAclBlob(), blob->publicAclBlobLength() + blob->cryptoBlobLength())
419 };
420 CSSM_ALGORITHMS verifyAlgorithm = CSSM_ALGID_SHA1HMAC;
421 #if defined(COMPAT_OSX_10_0)
422 if (blob->version() == blob->version_MacOS_10_0)
423 verifyAlgorithm = CSSM_ALGID_SHA1HMAC_LEGACY; // BSafe bug compatibility
424 #endif
425 VerifyMac verifier(Server::csp(), verifyAlgorithm);
426 verifier.key(mSigningKey);
427 CssmData signature(blob->blobSignature, sizeof(blob->blobSignature));
428 verifier.verify(signChunk, 2, signature);
429 }
430 /* else signature indicates cleartext */
431
432 // extract and hold some header bits the CSP does not want to see
433 uint32 heldAttributes = n2h(blob->header.attributes()) & managedAttributes;
434
435 CssmData privAclData;
436 if(inTheClear) {
437 /* NULL unwrap */
438 UnwrapKey unwrap(Server::csp(), CSSM_ALGID_NONE);
439 wrappedKey.clearAttribute(managedAttributes); //@@@ shouldn't be needed(?)
440 unwrap(wrappedKey,
441 KeySpec(n2h(blob->header.usage()),
442 (n2h(blob->header.attributes()) & ~managedAttributes) | forcedAttributes),
443 key, &privAclData);
444 }
445 else {
446 // decrypt the key using an unwrapping operation
447 UnwrapKey unwrap(Server::csp(), CSSM_ALGID_3DES_3KEY_EDE);
448 unwrap.key(mEncryptionKey);
449 unwrap.mode(CSSM_ALGMODE_CBCPadIV8);
450 unwrap.padding(CSSM_PADDING_PKCS1);
451 CssmData ivd(blob->iv, sizeof(blob->iv)); unwrap.initVector(ivd);
452 unwrap.add(CSSM_ATTRIBUTE_WRAPPED_KEY_FORMAT,
453 uint32(CSSM_KEYBLOB_WRAPPED_FORMAT_APPLE_CUSTOM));
454 wrappedKey.clearAttribute(managedAttributes); //@@@ shouldn't be needed(?)
455 unwrap(wrappedKey,
456 KeySpec(n2h(blob->header.usage()),
457 (n2h(blob->header.attributes()) & ~managedAttributes) | forcedAttributes),
458 key, &privAclData);
459 }
460
461 // compare retrieved key headers with blob headers (sanity check)
462 // @@@ this should probably be checked over carefully
463 CssmKey::Header &real = key.header();
464 CssmKey::Header &incoming = blob->header;
465 n2hi(incoming);
466
467 if (real.HeaderVersion != incoming.HeaderVersion ||
468 real.cspGuid() != incoming.cspGuid())
469 CssmError::throwMe(CSSMERR_CSP_INVALID_KEY);
470 if (real.algorithm() != incoming.algorithm())
471 CssmError::throwMe(CSSMERR_CSP_INVALID_ALGORITHM);
472
473 // re-insert held bits
474 key.header().KeyAttr |= heldAttributes;
475
476 if(inTheClear && (real.keyClass() != CSSM_KEYCLASS_PUBLIC_KEY)) {
477 /* Spoof - cleartext KeyBlob passed off as private key */
478 CssmError::throwMe(CSSMERR_CSP_INVALID_KEY);
479 }
480
481 // got a valid key: return the pieces
482 pubAcl = blob->publicAclBlob(); // points into blob (shared)
483 privAcl = privAclData; // was allocated by CSP decrypt, else NULL for
484 // cleatext keys
485 // key was set by unwrap operation
486 }
487
488
489 //
490 // Derive the blob-specific database blob encryption key from the passphrase and the salt.
491 //
492 CssmClient::Key DatabaseCryptoCore::deriveDbMasterKey(const CssmData &passphrase) const
493 {
494 // derive an encryption key and IV from passphrase and salt
495 CssmClient::DeriveKey makeKey(Server::csp(),
496 CSSM_ALGID_PKCS5_PBKDF2, CSSM_ALGID_3DES_3KEY_EDE, 24 * 8);
497 makeKey.iterationCount(1000);
498 CssmData salt = CssmData::wrap(mSalt);
499 makeKey.salt(salt);
500 CSSM_PKCS5_PBKDF2_PARAMS params;
501 params.Passphrase = passphrase;
502 params.PseudoRandomFunction = CSSM_PKCS5_PBKDF2_PRF_HMAC_SHA1;
503 CssmData paramData = CssmData::wrap(params);
504 return makeKey(&paramData, KeySpec(CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_DECRYPT,
505 CSSM_KEYATTR_RETURN_DATA | CSSM_KEYATTR_EXTRACTABLE));
506 }
507
508
509 //
510 // Turn raw keybits into a symmetric key in the CSP
511 //
512 CssmClient::Key DatabaseCryptoCore::makeRawKey(void *data, size_t length,
513 CSSM_ALGORITHMS algid, CSSM_KEYUSE usage)
514 {
515 // build a fake key
516 CssmKey key;
517 key.header().BlobType = CSSM_KEYBLOB_RAW;
518 key.header().Format = CSSM_KEYBLOB_RAW_FORMAT_OCTET_STRING;
519 key.header().AlgorithmId = algid;
520 key.header().KeyClass = CSSM_KEYCLASS_SESSION_KEY;
521 key.header().KeyUsage = usage;
522 key.header().KeyAttr = 0;
523 key.KeyData = CssmData(data, length);
524
525 // unwrap it into the CSP (but keep it raw)
526 UnwrapKey unwrap(Server::csp(), CSSM_ALGID_NONE);
527 CssmKey unwrappedKey;
528 CssmData descriptiveData;
529 unwrap(key,
530 KeySpec(CSSM_KEYUSE_ANY, CSSM_KEYATTR_RETURN_DATA | CSSM_KEYATTR_EXTRACTABLE),
531 unwrappedKey, &descriptiveData, NULL);
532 return CssmClient::Key(Server::csp(), unwrappedKey);
533 }