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) : &nothing;
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