1/* 2 * Copyright (c) 2000-2001,2011-2014 Apple Inc. All Rights Reserved. 3 * 4 * The contents of this file constitute Original Code as defined in and are 5 * subject to the Apple Public Source License Version 1.2 (the 'License'). 6 * You may not use this file except in compliance with the License. Please obtain 7 * a copy of the License at http://www.apple.com/publicsource and read it before 8 * using this file. 9 * 10 * This Original Code and all software distributed under the License are 11 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS 12 * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT 13 * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 14 * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the 15 * specific language governing rights and limitations under the License. 16 */ 17 18 19#include "securestorage.h" 20#include <security_cdsa_client/genkey.h> 21//#include <Security/Access.h> //@@@CONV 22#include <security_utilities/osxcode.h> 23#include <memory> 24 25using namespace CssmClient; 26//using namespace KeychainCore; 27 28// 29// Manage CSPDL attachments 30// 31CSPDLImpl::CSPDLImpl(const Guid &guid) 32: CSPImpl(Cssm::standard()->autoModule(guid)), 33DLImpl(CSPImpl::module()) 34{ 35} 36 37CSPDLImpl::CSPDLImpl(const Module &module) 38: CSPImpl(module), 39DLImpl(module) 40{ 41} 42 43CSPDLImpl::~CSPDLImpl() 44try 45{ 46} 47catch (...) 48{ 49} 50 51Allocator &CSPDLImpl::allocator() const 52{ 53 DLImpl::allocator(); return CSPImpl::allocator(); 54} 55 56void CSPDLImpl::allocator(Allocator &alloc) 57{ 58 CSPImpl::allocator(alloc); DLImpl::allocator(alloc); 59} 60 61bool CSPDLImpl::operator <(const CSPDLImpl &other) const 62{ 63 return (static_cast<const CSPImpl &>(*this) < static_cast<const CSPImpl &>(other) || 64 (!(static_cast<const CSPImpl &>(other) < static_cast<const CSPImpl &>(*this)) 65 && static_cast<const DLImpl &>(*this) < static_cast<const DLImpl &>(other))); 66} 67 68bool CSPDLImpl::operator ==(const CSPDLImpl &other) const 69{ 70 return (static_cast<const CSPImpl &>(*this) == static_cast<const CSPImpl &>(other) 71 && static_cast<const DLImpl &>(*this) == static_cast<const DLImpl &>(other)); 72} 73 74CSSM_SERVICE_MASK CSPDLImpl::subserviceMask() const 75{ 76 return CSPImpl::subserviceType() | DLImpl::subserviceType(); 77} 78 79void CSPDLImpl::subserviceId(uint32 id) 80{ 81 CSPImpl::subserviceId(id); DLImpl::subserviceId(id); 82} 83 84 85// 86// Secure storage 87// 88SSCSPDLImpl::SSCSPDLImpl(const Guid &guid) : CSPDLImpl::CSPDLImpl(guid) 89{ 90} 91 92SSCSPDLImpl::SSCSPDLImpl(const Module &module) : CSPDLImpl::CSPDLImpl(module) 93{ 94} 95 96SSCSPDLImpl::~SSCSPDLImpl() 97{ 98} 99 100DbImpl * 101SSCSPDLImpl::newDb(const char *inDbName, const CSSM_NET_ADDRESS *inDbLocation) 102{ 103 return new SSDbImpl(SSCSPDL(this), inDbName, inDbLocation); 104} 105 106 107// 108// SSDbImpl -- Secure Storage Database Implementation 109// 110SSDbImpl::SSDbImpl(const SSCSPDL &cspdl, const char *inDbName, 111 const CSSM_NET_ADDRESS *inDbLocation) 112: DbImpl(cspdl, inDbName, inDbLocation) 113{ 114} 115 116SSDbImpl::~SSDbImpl() 117{ 118} 119 120void 121SSDbImpl::create() 122{ 123 DbImpl::create(); 124} 125 126void 127SSDbImpl::open() 128{ 129 DbImpl::open(); 130} 131 132SSDbUniqueRecord 133SSDbImpl::insert(CSSM_DB_RECORDTYPE recordType, 134 const CSSM_DB_RECORD_ATTRIBUTE_DATA *attributes, 135 const CSSM_DATA *data, 136 const CSSM_RESOURCE_CONTROL_CONTEXT *rc) 137{ 138 // Get the handle of the DL underlying this CSPDL. 139 CSSM_DL_DB_HANDLE dldbh; 140 passThrough(CSSM_APPLECSPDL_DB_GET_HANDLE, NULL, 141 reinterpret_cast<void **>(&dldbh)); 142 143 // Turn off autocommit on the underlying DL and remember the old state. 144 CSSM_BOOL autoCommit = CSSM_TRUE; 145 check(CSSM_DL_PassThrough(dldbh, CSSM_APPLEFILEDL_TOGGLE_AUTOCOMMIT, 146 0, reinterpret_cast<void **>(&autoCommit))); 147 SSGroup group(SSDb(this), rc); 148 const CSSM_ACCESS_CREDENTIALS *cred = rc ? rc->AccessCred : NULL; 149 try 150 { 151 return insert(recordType, attributes, data, group, cred); 152 if (autoCommit) 153 { 154 // autoCommit was on so commit now that we are done and turn 155 // it back on. 156 check(CSSM_DL_PassThrough(dldbh, CSSM_APPLEFILEDL_COMMIT, NULL, NULL)); 157 CSSM_DL_PassThrough(dldbh, CSSM_APPLEFILEDL_TOGGLE_AUTOCOMMIT, 158 reinterpret_cast<const void *>(autoCommit), NULL); 159 } 160 } 161 catch(...) 162 { 163 try { group->deleteKey(cred); } catch (...) {} 164 if (autoCommit) 165 { 166 // autoCommit was off so rollback since we failed and turn 167 // autoCommit back on. 168 CSSM_DL_PassThrough(dldbh, CSSM_APPLEFILEDL_ROLLBACK, NULL, NULL); 169 CSSM_DL_PassThrough(dldbh, CSSM_APPLEFILEDL_TOGGLE_AUTOCOMMIT, 170 reinterpret_cast<const void *>(autoCommit), NULL); 171 } 172 throw; 173 } 174 175 // keep the compiler happy -- this path is NEVER taken 176 CssmError::throwMe(0); 177} 178 179SSDbUniqueRecord 180SSDbImpl::insert(CSSM_DB_RECORDTYPE recordType, 181 const CSSM_DB_RECORD_ATTRIBUTE_DATA *attributes, 182 const CSSM_DATA *data, const SSGroup &group, 183 const CSSM_ACCESS_CREDENTIALS *cred) 184{ 185 // Create an encoded dataBlob for this item. 186 CssmDataContainer dataBlob(allocator()); 187 group->encodeDataBlob(data, cred, dataBlob); 188 189 // Insert the record with the new juicy dataBlob. 190 return SSDbUniqueRecord(safe_cast<SSDbUniqueRecordImpl *> 191 (&(*DbImpl::insert(recordType, attributes, &dataBlob)))); 192} 193 194 195// DbCursorMaker 196DbCursorImpl * 197SSDbImpl::newDbCursor(const CSSM_QUERY &query, Allocator &allocator) 198{ 199 return new SSDbCursorImpl(Db(this), query, allocator); 200} 201 202DbCursorImpl * 203SSDbImpl::newDbCursor(uint32 capacity, Allocator &allocator) 204{ 205 return new SSDbCursorImpl(Db(this), capacity, allocator); 206} 207 208 209// SSDbUniqueRecordMaker 210DbUniqueRecordImpl * 211SSDbImpl::newDbUniqueRecord() 212{ 213 return new SSDbUniqueRecordImpl(Db(this)); 214} 215 216 217// 218// SSGroup -- Group key with acl, used to protect a group of items. 219// 220// @@@ Get this from a shared spot. 221CSSM_DB_NAME_ATTR(SSGroupImpl::kLabel, 6, (char*) "Label", 0, NULL, BLOB); 222 223// Create a new group. 224SSGroupImpl::SSGroupImpl(const SSDb &ssDb, 225 const CSSM_RESOURCE_CONTROL_CONTEXT *credAndAclEntry) 226: KeyImpl(ssDb->csp()), mLabel(ssDb->allocator()) 227{ 228 mLabel.Length = kLabelSize; 229 mLabel.Data = reinterpret_cast<uint8 *> 230 (mLabel.mAllocator.malloc(mLabel.Length)); 231 232 // Get our csp and set up a random number generation context. 233 CSP csp(this->csp()); 234 Random random(csp, CSSM_ALGID_APPLE_YARROW); 235 236 // Generate a kLabelSize byte random number that will be the label of 237 // the key which we store in the dataBlob. 238 random.generate(mLabel, (uint32)mLabel.Length); 239 240 // Overwrite the first 4 bytes with the magic cookie for a group. 241 reinterpret_cast<uint32 *>(mLabel.Data)[0] = h2n(uint32(kGroupMagic)); 242 243 // @@@ Ensure that the label is unique (Chance of collision is 2^80 -- 244 // birthday paradox). 245 246 // Generate a permanent 3DES key that we will use to encrypt the data. 247 GenerateKey genKey(csp, CSSM_ALGID_3DES_3KEY, 192); 248 genKey.database(ssDb); 249 250 // Set the acl of the key correctly here 251 genKey.rcc(credAndAclEntry); 252 253 // Generate the key 254 genKey(*this, KeySpec(CSSM_KEYUSE_ENCRYPT|CSSM_KEYUSE_DECRYPT, 255 CSSM_KEYATTR_PERMANENT|CSSM_KEYATTR_SENSITIVE, 256 mLabel)); 257 258 // Activate ourself so CSSM_FreeKey will get called when we go out of 259 // scope. 260 activate(); 261} 262 263// Lookup an existing group based on a dataBlob. 264SSGroupImpl::SSGroupImpl(const SSDb &ssDb, const CSSM_DATA &dataBlob) 265: KeyImpl(ssDb->csp()), mLabel(ssDb->allocator()) 266{ 267 if (dataBlob.Length < kLabelSize + kIVSize) 268 CssmError::throwMe(CSSMERR_DL_RECORD_NOT_FOUND); // Not a SS record 269 270 mLabel = CssmData(dataBlob.Data, kLabelSize); 271 if (*reinterpret_cast<const uint32 *>(mLabel.Data) != h2n (uint32(kGroupMagic))) 272 CssmError::throwMe(CSSMERR_DL_RECORD_NOT_FOUND); // Not a SS record 273 274 // Look up the symmetric key with that label. 275 DbCursor cursor(new DbDbCursorImpl(ssDb, 0, Allocator::standard())); 276 cursor->recordType(CSSM_DL_DB_RECORD_SYMMETRIC_KEY); 277 cursor->add(CSSM_DB_EQUAL, kLabel, mLabel); 278 279 DbUniqueRecord keyId; 280 CssmDataContainer keyData(ssDb->allocator()); 281 if (!cursor->next(NULL, &keyData, keyId)) 282 CssmError::throwMe(CSSMERR_DL_RECORD_NOT_FOUND); // The key can't be found 283 284 // Set the key part of ourself. 285 static_cast<CSSM_KEY &>(*this) = 286 *reinterpret_cast<const CSSM_KEY *>(keyData.Data); 287 288 // Activate ourself so CSSM_FreeKey will get called when we go out of 289 // scope. 290 activate(); 291} 292 293bool 294SSGroupImpl::isGroup(const CSSM_DATA &dataBlob) 295{ 296 return dataBlob.Length >= kLabelSize + kIVSize 297 && *reinterpret_cast<const uint32 *>(dataBlob.Data) == h2n(uint32(kGroupMagic)); 298} 299 300const CssmData 301SSGroupImpl::label() const 302{ 303 return mLabel; 304} 305 306void 307SSGroupImpl::decodeDataBlob(const CSSM_DATA &dataBlob, 308 const CSSM_ACCESS_CREDENTIALS *cred, 309 Allocator &allocator, CSSM_DATA &data) 310{ 311 // First get the IV and the cipherText from the blob. 312 CssmData iv(&dataBlob.Data[kLabelSize], kIVSize); 313 CssmData cipherText(&dataBlob.Data[kLabelSize + kIVSize], 314 dataBlob.Length - (kLabelSize + kIVSize)); 315 316 CssmDataContainer plainText1(allocator); 317 CssmDataContainer plainText2(allocator); 318 // Decrypt the data 319 // @@@ Don't use staged decrypt once the AppleCSPDL can do combo 320 // encryption. 321 // Setup decryption context 322 Decrypt decrypt(csp(), algorithm()); 323 decrypt.mode(CSSM_ALGMODE_CBCPadIV8); 324 decrypt.padding(CSSM_PADDING_PKCS1); 325 decrypt.initVector(iv); 326 decrypt.key(Key(this)); 327 decrypt.cred(AccessCredentials::overlay(cred)); 328 decrypt.decrypt(&cipherText, 1, &plainText1, 1); 329 decrypt.final(plainText2); 330 331 // Use DL allocator for allocating memory for data. 332 CSSM_SIZE length = plainText1.Length + plainText2.Length; 333 data.Data = allocator.alloc<uint8>((UInt32)length); 334 data.Length = length; 335 memcpy(data.Data, plainText1.Data, plainText1.Length); 336 memcpy(&data.Data[plainText1.Length], plainText2.Data, plainText2.Length); 337} 338 339void 340SSGroupImpl::encodeDataBlob(const CSSM_DATA *data, 341 const CSSM_ACCESS_CREDENTIALS *cred, 342 CssmDataContainer &dataBlob) 343{ 344 // Get our csp and set up a random number generation context. 345 CSP csp(this->csp()); 346 Random random(csp, CSSM_ALGID_APPLE_YARROW); 347 348 // Encrypt data using key and encode it in a dataBlob. 349 350 // First calculate a random IV. 351 uint8 ivBuf[kIVSize]; 352 CssmData iv(ivBuf, kIVSize); 353 random.generate(iv, kIVSize); 354 355 // Setup encryption context 356 Encrypt encrypt(csp, algorithm()); 357 encrypt.mode(CSSM_ALGMODE_CBCPadIV8); 358 encrypt.padding(CSSM_PADDING_PKCS1); 359 encrypt.initVector(iv); 360 encrypt.key(Key(this)); 361 encrypt.cred(AccessCredentials::overlay(cred)); 362 363 // Encrypt the data 364 const CssmData nothing; 365 const CssmData *plainText = data ? CssmData::overlay(data) : ¬hing; 366 // @@@ Don't use staged encrypt once the AppleCSPDL can do combo 367 // encryption. 368 CssmDataContainer cipherText1, cipherText2; 369 encrypt.encrypt(plainText, 1, &cipherText1, 1); 370 encrypt.final(cipherText2); 371 372 // Create a dataBlob containing the label followed by the IV followed 373 // by the cipherText. 374 CSSM_SIZE length = (kLabelSize + kIVSize 375 + cipherText1.Length + cipherText2.Length); 376 dataBlob.Data = dataBlob.mAllocator.alloc<uint8>((UInt32)length); 377 dataBlob.Length = length; 378 memcpy(dataBlob.Data, mLabel.Data, kLabelSize); 379 memcpy(&dataBlob.Data[kLabelSize], iv.Data, kIVSize); 380 memcpy(&dataBlob.Data[kLabelSize + kIVSize], 381 cipherText1.Data, cipherText1.Length); 382 memcpy(&dataBlob.Data[kLabelSize + kIVSize + cipherText1.Length], 383 cipherText2.Data, cipherText2.Length); 384} 385 386 387// 388// SSDbCursorImpl -- Secure Storage Database Cursor Implementation. 389// 390SSDbCursorImpl::SSDbCursorImpl(const Db &db, const CSSM_QUERY &query, 391 Allocator &allocator) 392: DbDbCursorImpl(db, query, allocator) 393{ 394} 395 396SSDbCursorImpl::SSDbCursorImpl(const Db &db, uint32 capacity, 397 Allocator &allocator) 398: DbDbCursorImpl(db, capacity, allocator) 399{ 400} 401 402bool 403SSDbCursorImpl::next(DbAttributes *attributes, ::CssmDataContainer *data, 404 DbUniqueRecord &uniqueId) 405{ 406 return next(attributes, data, uniqueId, NULL); 407} 408 409bool 410SSDbCursorImpl::next(DbAttributes *attributes, ::CssmDataContainer *data, 411 DbUniqueRecord &uniqueId, 412 const CSSM_ACCESS_CREDENTIALS *cred) 413{ 414 if (!data) 415 return DbDbCursorImpl::next(attributes, data, uniqueId); 416 417 DbAttributes noAttrs, *attrs; 418 attrs = attributes ? attributes : &noAttrs; 419 420 // Get the datablob for this record 421 CssmDataContainer dataBlob(allocator()); 422 for (;;) 423 { 424 if (!DbDbCursorImpl::next(attrs, &dataBlob, uniqueId)) 425 return false; 426 427 // Keep going until we find a non key type record. 428 CSSM_DB_RECORDTYPE rt = attrs->recordType(); 429 if (rt != CSSM_DL_DB_RECORD_SYMMETRIC_KEY 430 && rt != CSSM_DL_DB_RECORD_PRIVATE_KEY 431 && rt != CSSM_DL_DB_RECORD_PUBLIC_KEY) 432 { 433 // @@@ Check the label and if it doesn't start with the magic for a SSKey return the key. 434 break; 435 } 436 else 437 { 438 // Free the key we just retrieved 439 database()->csp()->freeKey(*reinterpret_cast<CssmKey *>(dataBlob.Data)); 440 } 441 } 442 443 if (!SSGroupImpl::isGroup(dataBlob)) 444 { 445 data->Data = dataBlob.Data; 446 data->Length = dataBlob.Length; 447 dataBlob.Data = NULL; 448 dataBlob.Length = 0; 449 return true; 450 } 451 452 // Get the group for dataBlob 453 SSGroup group(database(), dataBlob); 454 455 // Decode the dataBlob, pass in the DL allocator. 456 group->decodeDataBlob(dataBlob, cred, database()->allocator(), *data); 457 return true; 458} 459 460bool 461SSDbCursorImpl::nextKey(DbAttributes *attributes, Key &key, 462 DbUniqueRecord &uniqueId) 463{ 464 DbAttributes noAttrs, *attrs; 465 attrs = attributes ? attributes : &noAttrs; 466 CssmDataContainer keyData(database()->allocator()); 467 for (;;) 468 { 469 if (!DbDbCursorImpl::next(attrs, &keyData, uniqueId)) 470 return false; 471 // Keep going until we find a key type record. 472 CSSM_DB_RECORDTYPE rt = attrs->recordType(); 473 if (rt == CSSM_DL_DB_RECORD_SYMMETRIC_KEY 474 || rt == CSSM_DL_DB_RECORD_PRIVATE_KEY 475 || rt == CSSM_DL_DB_RECORD_PUBLIC_KEY) 476 break; 477 } 478 479 key = Key(database()->csp(), *reinterpret_cast<CSSM_KEY *>(keyData.Data)); 480 return true; 481} 482 483void 484SSDbCursorImpl::activate() 485{ 486 return DbDbCursorImpl::activate(); 487} 488 489void 490SSDbCursorImpl::deactivate() 491{ 492 return DbDbCursorImpl::deactivate(); 493} 494 495 496// 497// SSDbUniqueRecordImpl -- Secure Storage UniqueRecord Implementation. 498// 499SSDbUniqueRecordImpl::SSDbUniqueRecordImpl(const Db &db) 500: DbUniqueRecordImpl(db) 501{ 502} 503 504SSDbUniqueRecordImpl::~SSDbUniqueRecordImpl() 505{ 506} 507 508void 509SSDbUniqueRecordImpl::deleteRecord() 510{ 511 deleteRecord(NULL); 512} 513 514void 515SSDbUniqueRecordImpl::deleteRecord(const CSSM_ACCESS_CREDENTIALS *cred) 516{ 517 // Get the datablob for this record 518 // @@@ Fixme so we don't need to call DbUniqueRecordImpl::get 519 CssmDataContainer dataBlob(allocator()); 520 DbAttributes attributes; 521 522 DbUniqueRecordImpl::get(&attributes, &dataBlob); 523 CSSM_KEY_PTR keyPtr = (CSSM_KEY_PTR) dataBlob.data(); 524 525 // delete data part first: 526 // (1) don't leave data without keys around 527 // (2) delete orphaned data anyway 528 DbUniqueRecordImpl::deleteRecord(); 529 530 // @@@ Use transactions? 531 if (SSGroupImpl::isGroup(dataBlob)) 532 try { 533 // Get the group for dataBlob 534 SSGroup group(database(), dataBlob); 535 // Delete the group (key) 536 group->deleteKey(cred); 537 } catch (const CssmError &err) { 538 switch (err.error) { 539 case CSSMERR_DL_RECORD_NOT_FOUND: 540 // Zombie item (no group key). Finally at peace! No error 541 break; 542 default: 543 544 if (attributes.recordType() == CSSM_DL_DB_RECORD_PUBLIC_KEY || 545 attributes.recordType() == CSSM_DL_DB_RECORD_PRIVATE_KEY || 546 attributes.recordType() == CSSM_DL_DB_RECORD_SYMMETRIC_KEY) 547 { 548 allocator().free(keyPtr->KeyData.Data); 549 } 550 551 throw; 552 } 553 } 554 555 if (attributes.recordType() == CSSM_DL_DB_RECORD_PUBLIC_KEY || 556 attributes.recordType() == CSSM_DL_DB_RECORD_PRIVATE_KEY || 557 attributes.recordType() == CSSM_DL_DB_RECORD_SYMMETRIC_KEY) 558 { 559 allocator().free(keyPtr->KeyData.Data); 560 } 561} 562 563void 564SSDbUniqueRecordImpl::modify(CSSM_DB_RECORDTYPE recordType, 565 const CSSM_DB_RECORD_ATTRIBUTE_DATA *attributes, 566 const CSSM_DATA *data, 567 CSSM_DB_MODIFY_MODE modifyMode) 568{ 569 modify(recordType, attributes, data, modifyMode, NULL); 570} 571 572void 573SSDbUniqueRecordImpl::modify(CSSM_DB_RECORDTYPE recordType, 574 const CSSM_DB_RECORD_ATTRIBUTE_DATA *attributes, 575 const CSSM_DATA *data, 576 CSSM_DB_MODIFY_MODE modifyMode, 577 const CSSM_ACCESS_CREDENTIALS *cred) 578{ 579 if (!data) 580 { 581 DbUniqueRecordImpl::modify(recordType, attributes, NULL, modifyMode); 582 return; 583 } 584 585 // Get the datablob for this record 586 // @@@ Fixme so we don't need to call DbUniqueRecordImpl::get 587 CssmDataContainer oldDataBlob(allocator()); 588 DbUniqueRecordImpl::get(NULL, &oldDataBlob); 589 590 if (!SSGroupImpl::isGroup(oldDataBlob)) 591 { 592 DbUniqueRecordImpl::modify(recordType, attributes, data, modifyMode); 593 return; 594 } 595 596 // Get the group for oldDataBlob 597 SSGroup group(database(), oldDataBlob); 598 599 // Create a new dataBlob. 600 CssmDataContainer dataBlob(allocator()); 601 group->encodeDataBlob(data, cred, dataBlob); 602 DbUniqueRecordImpl::modify(recordType, attributes, &dataBlob, modifyMode); 603} 604 605void 606SSDbUniqueRecordImpl::get(DbAttributes *attributes, ::CssmDataContainer *data) 607{ 608 get(attributes, data, NULL); 609} 610 611void 612SSDbUniqueRecordImpl::get(DbAttributes *attributes, ::CssmDataContainer *data, 613 const CSSM_ACCESS_CREDENTIALS *cred) 614{ 615 if (!data) 616 { 617 DbUniqueRecordImpl::get(attributes, NULL); 618 return; 619 } 620 621 // Get the datablob for this record 622 // @@@ Fixme so we don't need to call DbUniqueRecordImpl::get 623 CssmDataContainer dataBlob(allocator()); 624 DbUniqueRecordImpl::get(attributes, &dataBlob); 625 626 if (!SSGroupImpl::isGroup(dataBlob)) 627 { 628 data->Data = dataBlob.Data; 629 data->Length = dataBlob.Length; 630 dataBlob.Data = NULL; 631 dataBlob.Length = 0; 632 return; 633 } 634 635 // Get the group for dataBlob 636 SSGroup group(database(), dataBlob); 637 638 // Decode the dataBlob, pass in the DL allocator. 639 group->decodeDataBlob(dataBlob, cred, allocator(), *data); 640} 641 642SSGroup 643SSDbUniqueRecordImpl::group() 644{ 645 // Get the datablob for this record 646 // @@@ Fixme so we don't need to call DbUniqueRecordImpl::get 647 CssmDataContainer dataBlob(allocator()); 648 DbUniqueRecordImpl::get(NULL, &dataBlob); 649 return SSGroup(database(), dataBlob); 650} 651