// // Database timeouts // @@@ Incomplete and not satisfactory // void timeouts() { printf("* Database timeout locks test\n"); CssmAllocator &alloc = CssmAllocator::standard(); ClientSession ss(alloc, alloc); DLDbIdentifier dbId1(ssuid, "/tmp/one", NULL); DLDbIdentifier dbId2(ssuid, "/tmp/two", NULL); DBParameters initialParams1 = { 4, false }; // 4 seconds timeout DBParameters initialParams2 = { 8, false }; // 8 seconds timeout // credential to set keychain passphrase AutoCredentials pwCred(alloc); StringData password("mumbojumbo"); pwCred += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_CHANGE_LOCK, new(alloc) ListElement(CSSM_SAMPLE_TYPE_PASSWORD), new(alloc) ListElement(password)); DbHandle db1 = ss.createDb(dbId1, &pwCred, NULL, initialParams1); DbHandle db2 = ss.createDb(dbId2, &pwCred, NULL, initialParams2); detail("Databases created"); // generate a key const CssmCryptoData seed(StringData("rain tonight")); FakeContext genContext(CSSM_ALGCLASS_KEYGEN, CSSM_ALGID_DES, &::Context::Attr(CSSM_ATTRIBUTE_KEY_LENGTH, 64), &::Context::Attr(CSSM_ATTRIBUTE_SEED, seed), NULL); KeyHandle key; CssmKey::Header header; ss.generateKey(db1, genContext, CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_DECRYPT, CSSM_KEYATTR_RETURN_REF | CSSM_KEYATTR_PERMANENT, /*cred*/NULL, NULL, key, header); ss.generateKey(db2, genContext, CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_DECRYPT, CSSM_KEYATTR_RETURN_REF | CSSM_KEYATTR_PERMANENT, /*cred*/NULL, NULL, key, header); detail("Keys generated and stored"); // credential to provide keychain passphrase AutoCredentials pwCred2(alloc); pwCred += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK, new(alloc) ListElement(CSSM_SAMPLE_TYPE_PASSWORD), new(alloc) ListElement(password)); //@@@ incomplete ss.releaseDb(db1); ss.releaseDb(db2); }
// // Given a (truncated) Database credentials TypedList specifying a master key, // locate the key and return a reference to it. // CssmClient::Key KeychainDatabase::keyFromCreds(const TypedList &sample, unsigned int requiredLength) { // decode TypedList structure (sample type; Data:CSPHandle; Data:CSSM_KEY) assert(sample.type() == CSSM_SAMPLE_TYPE_SYMMETRIC_KEY || sample.type() == CSSM_SAMPLE_TYPE_ASYMMETRIC_KEY); if (sample.length() != requiredLength || sample[1].type() != CSSM_LIST_ELEMENT_DATUM || sample[2].type() != CSSM_LIST_ELEMENT_DATUM || (requiredLength == 4 && sample[3].type() != CSSM_LIST_ELEMENT_DATUM)) CssmError::throwMe(CSSM_ERRCODE_INVALID_SAMPLE_VALUE); KeyHandle &handle = *sample[1].data().interpretedAs<KeyHandle>(CSSM_ERRCODE_INVALID_SAMPLE_VALUE); // We used to be able to check the length but supporting multiple client // architectures dishes that (sizeof(CSSM_KEY) varies due to alignment and // field-size differences). The decoding in the transition layer should // serve as a sufficient garbling check anyway. if (sample[2].data().data() == NULL) CssmError::throwMe(CSSM_ERRCODE_INVALID_SAMPLE_VALUE); CssmKey &key = *sample[2].data().interpretedAs<CssmKey>(); if (key.header().cspGuid() == gGuidAppleCSPDL) { // handleOrKey is a SecurityServer KeyHandle; ignore key argument return safer_cast<LocalKey &>(*Server::key(handle)); } else if (sample.type() == CSSM_SAMPLE_TYPE_ASYMMETRIC_KEY) { /* Contents (see DefaultCredentials::unlockKey in libsecurity_keychain/defaultcreds.cpp) sample[0] sample type sample[1] csp handle for master or wrapping key; is really a keyhandle sample[2] masterKey [not used since securityd cannot interpret; use sample[1] handle instead] sample[3] UnlockReferralRecord data, in this case the flattened symmetric key */ // RefPointer<Key> Server::key(KeyHandle key) KeyHandle keyhandle = *sample[1].data().interpretedAs<KeyHandle>(CSSM_ERRCODE_INVALID_SAMPLE_VALUE); CssmData &flattenedKey = sample[3].data(); RefPointer<Key> unwrappingKey = Server::key(keyhandle); Database &db=unwrappingKey->database(); CssmKey rawWrappedKey; unflattenKey(flattenedKey, rawWrappedKey); RefPointer<Key> masterKey; CssmData emptyDescriptiveData; const AccessCredentials *cred = NULL; const AclEntryPrototype *owner = NULL; CSSM_KEYUSE usage = CSSM_KEYUSE_ANY; CSSM_KEYATTR_FLAGS attrs = CSSM_KEYATTR_EXTRACTABLE; //CSSM_KEYATTR_RETURN_REF | // Get default credentials for unwrappingKey (the one on the token) // Copied from Statics::Statics() in libsecurity_keychain/aclclient.cpp // Following KeyItem::getCredentials, one sees that the "operation" parameter // e.g. "CSSM_ACL_AUTHORIZATION_DECRYPT" is ignored Allocator &alloc = Allocator::standard(); AutoCredentials promptCred(alloc, 3);// enable interactive prompting // promptCred: a credential permitting user prompt confirmations // contains: // a KEYCHAIN_PROMPT sample, both by itself and in a THRESHOLD // a PROMPTED_PASSWORD sample promptCred.sample(0) = TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_PROMPT); promptCred.sample(1) = TypedList(alloc, CSSM_SAMPLE_TYPE_THRESHOLD, new(alloc) ListElement(TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_PROMPT))); promptCred.sample(2) = TypedList(alloc, CSSM_SAMPLE_TYPE_PROMPTED_PASSWORD, new(alloc) ListElement(alloc, CssmData())); // This unwrap object is here just to provide a context CssmClient::UnwrapKey unwrap(Server::csp(), CSSM_ALGID_NONE); //ok to lie about csp here unwrap.mode(CSSM_ALGMODE_NONE); unwrap.padding(CSSM_PADDING_PKCS1); unwrap.cred(promptCred); unwrap.add(CSSM_ATTRIBUTE_WRAPPED_KEY_FORMAT, uint32(CSSM_KEYBLOB_WRAPPED_FORMAT_PKCS7)); Security::Context *tmpContext; CSSM_CC_HANDLE CCHandle = unwrap.handle(); /*CSSM_RETURN rx = */ CSSM_GetContext (CCHandle, (CSSM_CONTEXT_PTR *)&tmpContext); // OK, this is skanky but necessary. We overwrite fields in the context struct tmpContext->ContextType = CSSM_ALGCLASS_ASYMMETRIC; tmpContext->AlgorithmType = CSSM_ALGID_RSA; db.unwrapKey(*tmpContext, cred, owner, unwrappingKey, NULL, usage, attrs, rawWrappedKey, masterKey, emptyDescriptiveData); Allocator::standard().free(rawWrappedKey.KeyData.Data); return safer_cast<LocalKey &>(*masterKey).key(); } else { // not a KeyHandle reference; use key as a raw key if (key.header().blobType() != CSSM_KEYBLOB_RAW) CssmError::throwMe(CSSMERR_CSP_INVALID_KEY_REFERENCE); if (key.header().keyClass() != CSSM_KEYCLASS_SESSION_KEY) CssmError::throwMe(CSSMERR_CSP_INVALID_KEY_CLASS); return CssmClient::Key(Server::csp(), key, true); } }
CssmList OriginAclSubject::toList(Allocator &alloc) const { return TypedList(alloc, CSSM_ACL_SUBJECT_TYPE_PREAUTH, new(alloc) ListElement(mAuthTag)); }
CssmList SourceAclSubject::toList(Allocator &alloc) const { return TypedList(alloc, CSSM_ACL_SUBJECT_TYPE_PREAUTH_SOURCE, new(alloc) ListElement(mSourceSubject->toList(alloc))); }
// // Database tests. // Database locks/unlocks etc. // void databases() { printf("* Database manipulation test\n"); CssmAllocator &alloc = CssmAllocator::standard(); ClientSession ss(alloc, alloc); AutoCredentials pwCred(alloc); StringData passphrase("two"); StringData badPassphrase("three"); pwCred += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_CHANGE_LOCK, new(alloc) ListElement(CSSM_SAMPLE_TYPE_PASSWORD), new(alloc) ListElement(passphrase)); pwCred += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK, new(alloc) ListElement(CSSM_SAMPLE_TYPE_PASSWORD), new(alloc) ListElement(badPassphrase)); // pwCred = (NEW: two, OLD: three) DbTester db1(ss, "/tmp/one", NULL, 30, true); DbTester db2(ss, "/tmp/two", &pwCred, 60, false); // db2.passphrase = two // encode db1 and re-open it CssmData dbBlob; ss.encodeDb(db1, dbBlob); DbHandle db1b = ss.decodeDb(db1.dbId, &nullCred, dbBlob); if (db1b == db1.dbRef) detail("REUSED DB HANDLE ON DECODEDB (probably wrong)"); // open db1 a third time (so now there's three db handles for db1) DbHandle db1c = ss.decodeDb(db1.dbId, &nullCred, dbBlob); // lock them to get started ss.lock(db1); ss.lock(db2); // unlock it through user prompt("unlock"); ss.unlock(db1); prompt(); ss.unlock(db1b); // 2nd unlock should not prompt ss.lock(db1c); // lock it again prompt("unlock"); ss.unlock(db1); // and that should prompt again prompt(); // db2 has a passphrase lock credentials - it'll work without U/I db2.unlock("wrong passphrase"); // pw=two, cred=three AutoCredentials pwCred2(alloc); pwCred2 += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK, new(alloc) ListElement(CSSM_SAMPLE_TYPE_PASSWORD), new(alloc) ListElement(passphrase)); // pwCred2 = (OLD: two) ss.authenticateDb(db2, CSSM_DB_ACCESS_WRITE, &pwCred2); // set it db2.unlock(); ss.lock(db2); // now change db2's passphrase ss.lock(db2); pwCred2 += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_CHANGE_LOCK, new(alloc) ListElement(CSSM_SAMPLE_TYPE_PASSWORD), new(alloc) ListElement(badPassphrase)); // pwCred2 = (OLD: two, NEW: three) db2.changePassphrase(&pwCred2); // passphrase = three, cred = (OLD: two) // encode and re-decode to make sure new data is there CssmData blob2; ss.encodeDb(db2, blob2); DbHandle db2a = ss.decodeDb(db2.dbId, &pwCred, blob2); // db2a cred = (OLD: two, NEW: three) // now, the *old* cred won't work anymore db2.unlock("old passphrase accepted"); // back to the old credentials, which *do* have the (old bad, now good) passphrase ss.lock(db2a); ss.unlock(db2a); detail("New passphrase accepted"); // clear the credentials (this will prompt; cancel it) ss.authenticateDb(db2, CSSM_DB_ACCESS_WRITE, NULL); prompt("cancel"); db2.unlock("null credential accepted"); prompt(); // fell-swoop from-to change password operation StringData newPassphrase("hollerith"); AutoCredentials pwCred3(alloc); pwCred3 += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_CHANGE_LOCK, new(alloc) ListElement(CSSM_SAMPLE_TYPE_PASSWORD), new(alloc) ListElement(newPassphrase)); pwCred3 += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK, new(alloc) ListElement(CSSM_SAMPLE_TYPE_PASSWORD), new(alloc) ListElement(passphrase)); db2.changePassphrase(&pwCred3, "accepting original (unchanged) passphrase"); AutoCredentials pwCred4(alloc); pwCred4 += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_CHANGE_LOCK, new(alloc) ListElement(CSSM_SAMPLE_TYPE_PASSWORD), new(alloc) ListElement(newPassphrase)); pwCred4 += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK, new(alloc) ListElement(CSSM_SAMPLE_TYPE_PASSWORD), new(alloc) ListElement(badPassphrase)); db2.changePassphrase(&pwCred4); // final status check AutoCredentials pwCred5(alloc); pwCred5 += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK, new(alloc) ListElement(CSSM_SAMPLE_TYPE_PASSWORD), new(alloc) ListElement(newPassphrase)); ss.authenticateDb(db2, CSSM_DB_ACCESS_WRITE, &pwCred5); db2.unlock(); detail("Final passphrase change verified"); }
// // Key encryption tests. // void keyBlobs() { printf("* Keyblob encryption test\n"); CssmAllocator &alloc = CssmAllocator::standard(); ClientSession ss(alloc, alloc); DLDbIdentifier dbId1(ssuid, "/tmp/one", NULL); DBParameters initialParams1 = { 3600, false }; // create a new database DbHandle db = ss.createDb(dbId1, NULL, NULL, initialParams1); detail("Database created"); // establish an ACL for the key StringData theAclPassword("Strenge Geheimsache"); AclEntryPrototype initialAcl; initialAcl.TypedSubject = TypedList(alloc, CSSM_ACL_SUBJECT_TYPE_PASSWORD, new(alloc) ListElement(theAclPassword)); AclEntryInput initialAclInput(initialAcl); AutoCredentials cred(alloc); cred += TypedList(alloc, CSSM_SAMPLE_TYPE_PASSWORD, new(alloc) ListElement(theAclPassword)); // generate a key const CssmCryptoData seed(StringData("Farmers' day")); FakeContext genContext(CSSM_ALGCLASS_KEYGEN, CSSM_ALGID_DES, &::Context::Attr(CSSM_ATTRIBUTE_KEY_LENGTH, 64), &::Context::Attr(CSSM_ATTRIBUTE_SEED, seed), NULL); KeyHandle key; CssmKey::Header header; ss.generateKey(db, genContext, CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_DECRYPT, CSSM_KEYATTR_RETURN_REF | CSSM_KEYATTR_PERMANENT, /*cred*/NULL, &initialAclInput, key, header); detail("Key generated"); // encrypt with the key StringData clearText("Yet another boring cleartext sample string text sequence."); StringData iv("Aardvark"); CssmKey nullKey; memset(&nullKey, 0, sizeof(nullKey)); FakeContext cryptoContext(CSSM_ALGCLASS_SYMMETRIC, CSSM_ALGID_DES, &::Context::Attr(CSSM_ATTRIBUTE_KEY, nullKey), &::Context::Attr(CSSM_ATTRIBUTE_INIT_VECTOR, iv), &::Context::Attr(CSSM_ATTRIBUTE_MODE, CSSM_ALGMODE_CBC_IV8), &::Context::Attr(CSSM_ATTRIBUTE_PADDING, CSSM_PADDING_PKCS1), &::Context::Attr(CSSM_ATTRIBUTE_ACCESS_CREDENTIALS, cred), NULL); CssmData cipherText; ss.encrypt(cryptoContext, key, clearText, cipherText); detail("Plaintext encrypted with original key"); // encode the key and release it CssmData blob; ss.encodeKey(key, blob); ss.releaseKey(key); detail("Key encoded and released"); // decode it again, re-introducing it CssmKey::Header decodedHeader; KeyHandle key2 = ss.decodeKey(db, blob, decodedHeader); detail("Key decoded"); // decrypt with decoded key CssmData recovered; ss.decrypt(cryptoContext, key2, cipherText, recovered); assert(recovered == clearText); detail("Decoded key correctly decrypts ciphertext"); // check a few header fields if (!memcmp(&header, &decodedHeader, sizeof(header))) { detail("All header fields match"); } else { assert(header.algorithm() == decodedHeader.algorithm()); assert(header.blobType() == decodedHeader.blobType()); assert(header.blobFormat() == decodedHeader.blobFormat()); assert(header.keyClass() == decodedHeader.keyClass()); assert(header.attributes() == decodedHeader.attributes()); assert(header.usage() == decodedHeader.usage()); printf("Some header fields differ (probably okay)\n"); } // make sure we need the credentials (destructive) memset(&cred, 0, sizeof(cred)); try { ss.decrypt(cryptoContext, key2, cipherText, recovered); error("RESTORED ACL FAILS TO RESTRICT"); } catch (CssmError &err) { detail(err, "Restored key restricts access properly"); } }
// // Make a copy of this subject in CSSM_LIST form // CssmList PasswordAclSubject::toList(Allocator &alloc) const { // the password itself is private and not exported to CSSM return TypedList(alloc, CSSM_ACL_SUBJECT_TYPE_PASSWORD); }