/* * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ // // dbcrypto - cryptographic core for database and key blob cryptography // #include "dbcrypto.h" #include #include "server.h" // just for Server::csp() #include #include #include #include #include #include using namespace CssmClient; using LowLevelMemoryUtilities::fieldOffsetOf; // // The CryptoCore constructor doesn't do anything interesting. // It just initializes us to "empty". // DatabaseCryptoCore::DatabaseCryptoCore() : mHaveMaster(false), mIsValid(false) { } DatabaseCryptoCore::~DatabaseCryptoCore() { // key objects take care of themselves } // // Forget the secrets // void DatabaseCryptoCore::invalidate() { mMasterKey.release(); mHaveMaster = false; mEncryptionKey.release(); mSigningKey.release(); mIsValid = false; } // // Generate new secrets for this crypto core. // void DatabaseCryptoCore::generateNewSecrets() { // create a random DES3 key GenerateKey desGenerator(Server::csp(), CSSM_ALGID_3DES_3KEY_EDE, 24 * 8); mEncryptionKey = desGenerator(KeySpec(CSSM_KEYUSE_WRAP | CSSM_KEYUSE_UNWRAP, CSSM_KEYATTR_RETURN_DATA | CSSM_KEYATTR_EXTRACTABLE)); // create a random 20 byte HMAC/SHA1 signing "key" GenerateKey signGenerator(Server::csp(), CSSM_ALGID_SHA1HMAC, sizeof(DbBlob::PrivateBlob::SigningKey) * 8); mSigningKey = signGenerator(KeySpec(CSSM_KEYUSE_SIGN | CSSM_KEYUSE_VERIFY, CSSM_KEYATTR_RETURN_DATA | CSSM_KEYATTR_EXTRACTABLE)); // secrets established mIsValid = true; } CssmClient::Key DatabaseCryptoCore::masterKey() { assert(mHaveMaster); return mMasterKey; } // // Establish the master secret as derived from a passphrase passed in. // If a DbBlob is passed, take the salt from it and remember it. // If a NULL DbBlob is passed, generate a new (random) salt. // Note that the passphrase is NOT remembered; only the master key. // void DatabaseCryptoCore::setup(const DbBlob *blob, const CssmData &passphrase) { if (blob) memcpy(mSalt, blob->salt, sizeof(mSalt)); else Server::active().random(mSalt); mMasterKey = deriveDbMasterKey(passphrase); mHaveMaster = true; } // // Establish the master secret directly from a master key passed in. // We will copy the KeyData (caller still owns its copy). // Blob/salt handling as above. // void DatabaseCryptoCore::setup(const DbBlob *blob, CssmClient::Key master) { // pre-screen the key CssmKey::Header header = master.header(); if (header.keyClass() != CSSM_KEYCLASS_SESSION_KEY) CssmError::throwMe(CSSMERR_CSP_INVALID_KEY_CLASS); if (header.algorithm() != CSSM_ALGID_3DES_3KEY_EDE) CssmError::throwMe(CSSMERR_CSP_INVALID_ALGORITHM); // accept it if (blob) memcpy(mSalt, blob->salt, sizeof(mSalt)); else Server::active().random(mSalt); mMasterKey = master; mHaveMaster = true; } // // Given a putative passphrase, determine whether that passphrase // properly generates the database's master secret. // Return a boolean accordingly. Do not change our state. // The database must have a master secret (to compare with). // Note that any errors thrown by the cryptography here will actually // throw out of validatePassphrase, since they "should not happen" and // thus indicate a problem *beyond* (just) a bad passphrase. // bool DatabaseCryptoCore::validatePassphrase(const CssmData &passphrase) { assert(hasMaster()); CssmClient::Key master = deriveDbMasterKey(passphrase); // to compare master with mMaster, see if they encrypt alike StringData probe ("Now is the time for all good processes to come to the aid of their kernel."); CssmData noRemainder((void *)1, 0); // no cipher overflow Encrypt cryptor(Server::csp(), CSSM_ALGID_3DES_3KEY_EDE); cryptor.mode(CSSM_ALGMODE_CBCPadIV8); cryptor.padding(CSSM_PADDING_PKCS1); uint8 iv[8]; // leave uninitialized; pseudo-random is cool cryptor.initVector(CssmData::wrap(iv)); cryptor.key(master); CssmAutoData cipher1(Server::csp().allocator()); cryptor.encrypt(probe, cipher1.get(), noRemainder); cryptor.key(mMasterKey); CssmAutoData cipher2(Server::csp().allocator()); cryptor.encrypt(probe, cipher2.get(), noRemainder); return cipher1 == cipher2; } // // Encode a database blob from the core. // DbBlob *DatabaseCryptoCore::encodeCore(const DbBlob &blobTemplate, const CssmData &publicAcl, const CssmData &privateAcl) const { assert(isValid()); // must have secrets to work from // make a new IV uint8 iv[8]; Server::active().random(iv); // build the encrypted section blob CssmData &encryptionBits = *mEncryptionKey; CssmData &signingBits = *mSigningKey; CssmData incrypt[3]; incrypt[0] = encryptionBits; incrypt[1] = signingBits; incrypt[2] = privateAcl; CssmData cryptoBlob, remData; Encrypt cryptor(Server::csp(), CSSM_ALGID_3DES_3KEY_EDE); cryptor.mode(CSSM_ALGMODE_CBCPadIV8); cryptor.padding(CSSM_PADDING_PKCS1); cryptor.key(mMasterKey); CssmData ivd(iv, sizeof(iv)); cryptor.initVector(ivd); cryptor.encrypt(incrypt, 3, &cryptoBlob, 1, remData); // allocate the final DbBlob, uh, blob size_t length = sizeof(DbBlob) + publicAcl.length() + cryptoBlob.length(); DbBlob *blob = Allocator::standard().malloc(length); // assemble the DbBlob memset(blob, 0x7d, sizeof(DbBlob)); // deterministically fill any alignment gaps blob->initialize(); blob->randomSignature = blobTemplate.randomSignature; blob->sequence = blobTemplate.sequence; blob->params = blobTemplate.params; memcpy(blob->salt, mSalt, sizeof(blob->salt)); memcpy(blob->iv, iv, sizeof(iv)); memcpy(blob->publicAclBlob(), publicAcl, publicAcl.length()); blob->startCryptoBlob = sizeof(DbBlob) + publicAcl.length(); memcpy(blob->cryptoBlob(), cryptoBlob, cryptoBlob.length()); blob->totalLength = blob->startCryptoBlob + cryptoBlob.length(); // sign the blob CssmData signChunk[] = { CssmData(blob->data(), fieldOffsetOf(&DbBlob::blobSignature)), CssmData(blob->publicAclBlob(), publicAcl.length() + cryptoBlob.length()) }; CssmData signature(blob->blobSignature, sizeof(blob->blobSignature)); GenerateMac signer(Server::csp(), CSSM_ALGID_SHA1HMAC_LEGACY); signer.key(mSigningKey); signer.sign(signChunk, 2, signature); assert(signature.length() == sizeof(blob->blobSignature)); // all done. Clean up Server::csp()->allocator().free(cryptoBlob); return blob; } // // Decode a database blob into the core. // Throws exceptions if decoding fails. // Memory returned in privateAclBlob is allocated and becomes owned by caller. // void DatabaseCryptoCore::decodeCore(DbBlob *blob, void **privateAclBlob) { assert(mHaveMaster); // must have master key installed // try to decrypt the cryptoblob section Decrypt decryptor(Server::csp(), CSSM_ALGID_3DES_3KEY_EDE); decryptor.mode(CSSM_ALGMODE_CBCPadIV8); decryptor.padding(CSSM_PADDING_PKCS1); decryptor.key(mMasterKey); CssmData ivd(blob->iv, sizeof(blob->iv)); decryptor.initVector(ivd); CssmData cryptoBlob(blob->cryptoBlob(), blob->cryptoBlobLength()); CssmData decryptedBlob, remData; decryptor.decrypt(cryptoBlob, decryptedBlob, remData); DbBlob::PrivateBlob *privateBlob = decryptedBlob.interpretedAs(); // tentatively establish keys mEncryptionKey = makeRawKey(privateBlob->encryptionKey, sizeof(privateBlob->encryptionKey), CSSM_ALGID_3DES_3KEY_EDE, CSSM_KEYUSE_WRAP | CSSM_KEYUSE_UNWRAP); mSigningKey = makeRawKey(privateBlob->signingKey, sizeof(privateBlob->signingKey), CSSM_ALGID_SHA1HMAC, CSSM_KEYUSE_SIGN | CSSM_KEYUSE_VERIFY); // verify signature on the whole blob CssmData signChunk[] = { CssmData(blob->data(), fieldOffsetOf(&DbBlob::blobSignature)), CssmData(blob->publicAclBlob(), blob->publicAclBlobLength() + blob->cryptoBlobLength()) }; CSSM_ALGORITHMS verifyAlgorithm = CSSM_ALGID_SHA1HMAC; #if defined(COMPAT_OSX_10_0) if (blob->version() == blob->version_MacOS_10_0) verifyAlgorithm = CSSM_ALGID_SHA1HMAC_LEGACY; // BSafe bug compatibility #endif VerifyMac verifier(Server::csp(), verifyAlgorithm); verifier.key(mSigningKey); verifier.verify(signChunk, 2, CssmData(blob->blobSignature, sizeof(blob->blobSignature))); // all checks out; start extracting fields if (privateAclBlob) { // extract private ACL blob as a separately allocated area uint32 blobLength = decryptedBlob.length() - sizeof(DbBlob::PrivateBlob); *privateAclBlob = Allocator::standard().malloc(blobLength); memcpy(*privateAclBlob, privateBlob->privateAclBlob(), blobLength); } // secrets have been established mIsValid = true; Allocator::standard().free(privateBlob); } // // Make another DatabaseCryptoCore's operational secrets our own. // Intended for keychain synchronization. // void DatabaseCryptoCore::importSecrets(const DatabaseCryptoCore &src) { assert(src.isValid()); // must have called src.decodeCore() first assert(hasMaster()); mEncryptionKey = src.mEncryptionKey; mSigningKey = src.mSigningKey; mIsValid = true; } // // Encode a key blob // KeyBlob *DatabaseCryptoCore::encodeKeyCore(const CssmKey &inKey, const CssmData &publicAcl, const CssmData &privateAcl) const { assert(isValid()); // need our database secrets // create new IV uint8 iv[8]; Server::active().random(iv); // extract and hold some header bits the CSP does not want to see CssmKey key = inKey; uint32 heldAttributes = key.attributes() & managedAttributes; key.clearAttribute(managedAttributes); key.setAttribute(forcedAttributes); // use a CMS wrap to encrypt the key WrapKey wrap(Server::csp(), CSSM_ALGID_3DES_3KEY_EDE); wrap.key(mEncryptionKey); wrap.mode(CSSM_ALGMODE_CBCPadIV8); wrap.padding(CSSM_PADDING_PKCS1); CssmData ivd(iv, sizeof(iv)); wrap.initVector(ivd); wrap.add(CSSM_ATTRIBUTE_WRAPPED_KEY_FORMAT, uint32(CSSM_KEYBLOB_WRAPPED_FORMAT_APPLE_CUSTOM)); CssmKey wrappedKey; wrap(key, wrappedKey, &privateAcl); // stick the held attribute bits back in key.clearAttribute(forcedAttributes); key.setAttribute(heldAttributes); // allocate the final KeyBlob, uh, blob size_t length = sizeof(KeyBlob) + publicAcl.length() + wrappedKey.length(); KeyBlob *blob = Allocator::standard().malloc(length); // assemble the KeyBlob memset(blob, 0, sizeof(KeyBlob)); // fill alignment gaps blob->initialize(); memcpy(blob->iv, iv, sizeof(iv)); blob->header = key.header(); h2ni(blob->header); // endian-correct the header blob->wrappedHeader.blobType = wrappedKey.blobType(); blob->wrappedHeader.blobFormat = wrappedKey.blobFormat(); blob->wrappedHeader.wrapAlgorithm = wrappedKey.wrapAlgorithm(); blob->wrappedHeader.wrapMode = wrappedKey.wrapMode(); memcpy(blob->publicAclBlob(), publicAcl, publicAcl.length()); blob->startCryptoBlob = sizeof(KeyBlob) + publicAcl.length(); memcpy(blob->cryptoBlob(), wrappedKey.data(), wrappedKey.length()); blob->totalLength = blob->startCryptoBlob + wrappedKey.length(); // sign the blob CssmData signChunk[] = { CssmData(blob->data(), fieldOffsetOf(&KeyBlob::blobSignature)), CssmData(blob->publicAclBlob(), blob->publicAclBlobLength() + blob->cryptoBlobLength()) }; CssmData signature(blob->blobSignature, sizeof(blob->blobSignature)); GenerateMac signer(Server::csp(), CSSM_ALGID_SHA1HMAC_LEGACY); //@@@!!! CRUD signer.key(mSigningKey); signer.sign(signChunk, 2, signature); assert(signature.length() == sizeof(blob->blobSignature)); // all done. Clean up Server::csp()->allocator().free(wrappedKey); return blob; } // // Decode a key blob // void DatabaseCryptoCore::decodeKeyCore(KeyBlob *blob, CssmKey &key, void * &pubAcl, void * &privAcl) const { assert(isValid()); // need our database secrets // Assemble the encrypted blob as a CSSM "wrapped key" CssmKey wrappedKey; wrappedKey.KeyHeader = blob->header; h2ni(wrappedKey.KeyHeader); wrappedKey.blobType(blob->wrappedHeader.blobType); wrappedKey.blobFormat(blob->wrappedHeader.blobFormat); wrappedKey.wrapAlgorithm(blob->wrappedHeader.wrapAlgorithm); wrappedKey.wrapMode(blob->wrappedHeader.wrapMode); wrappedKey.KeyData = CssmData(blob->cryptoBlob(), blob->cryptoBlobLength()); // verify signature (check against corruption) CssmData signChunk[] = { CssmData::wrap(blob, fieldOffsetOf(&KeyBlob::blobSignature)), CssmData(blob->publicAclBlob(), blob->publicAclBlobLength() + blob->cryptoBlobLength()) }; CSSM_ALGORITHMS verifyAlgorithm = CSSM_ALGID_SHA1HMAC; #if defined(COMPAT_OSX_10_0) if (blob->version() == blob->version_MacOS_10_0) verifyAlgorithm = CSSM_ALGID_SHA1HMAC_LEGACY; // BSafe bug compatibility #endif VerifyMac verifier(Server::csp(), verifyAlgorithm); verifier.key(mSigningKey); CssmData signature(blob->blobSignature, sizeof(blob->blobSignature)); verifier.verify(signChunk, 2, signature); // extract and hold some header bits the CSP does not want to see uint32 heldAttributes = n2h(blob->header.attributes()) & managedAttributes; // decrypt the key using an unwrapping operation UnwrapKey unwrap(Server::csp(), CSSM_ALGID_3DES_3KEY_EDE); unwrap.key(mEncryptionKey); unwrap.mode(CSSM_ALGMODE_CBCPadIV8); unwrap.padding(CSSM_PADDING_PKCS1); CssmData ivd(blob->iv, sizeof(blob->iv)); unwrap.initVector(ivd); unwrap.add(CSSM_ATTRIBUTE_WRAPPED_KEY_FORMAT, uint32(CSSM_KEYBLOB_WRAPPED_FORMAT_APPLE_CUSTOM)); CssmData privAclData; wrappedKey.clearAttribute(managedAttributes); //@@@ shouldn't be needed(?) unwrap(wrappedKey, KeySpec(n2h(blob->header.usage()), (n2h(blob->header.attributes()) & ~managedAttributes) | forcedAttributes), key, &privAclData); // compare retrieved key headers with blob headers (sanity check) // @@@ this should probably be checked over carefully CssmKey::Header &real = key.header(); CssmKey::Header &incoming = blob->header; n2hi(incoming); if (real.HeaderVersion != incoming.HeaderVersion || real.cspGuid() != incoming.cspGuid()) CssmError::throwMe(CSSMERR_CSP_INVALID_KEY); if (real.algorithm() != incoming.algorithm()) CssmError::throwMe(CSSMERR_CSP_INVALID_ALGORITHM); // re-insert held bits key.header().KeyAttr |= heldAttributes; // got a valid key: return the pieces pubAcl = blob->publicAclBlob(); // points into blob (shared) privAcl = privAclData; // was allocated by CSP decrypt // key was set by unwrap operation } // // Derive the blob-specific database blob encryption key from the passphrase and the salt. // CssmClient::Key DatabaseCryptoCore::deriveDbMasterKey(const CssmData &passphrase) const { // derive an encryption key and IV from passphrase and salt CssmClient::DeriveKey makeKey(Server::csp(), CSSM_ALGID_PKCS5_PBKDF2, CSSM_ALGID_3DES_3KEY_EDE, 24 * 8); makeKey.iterationCount(1000); CssmData salt = CssmData::wrap(mSalt); makeKey.salt(salt); CSSM_PKCS5_PBKDF2_PARAMS params; params.Passphrase = passphrase; params.PseudoRandomFunction = CSSM_PKCS5_PBKDF2_PRF_HMAC_SHA1; CssmData paramData = CssmData::wrap(params); return makeKey(¶mData, KeySpec(CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_DECRYPT, CSSM_KEYATTR_RETURN_DATA | CSSM_KEYATTR_EXTRACTABLE)); } // // Turn raw keybits into a symmetric key in the CSP // CssmClient::Key DatabaseCryptoCore::makeRawKey(void *data, size_t length, CSSM_ALGORITHMS algid, CSSM_KEYUSE usage) { // build a fake key CssmKey key; key.header().BlobType = CSSM_KEYBLOB_RAW; key.header().Format = CSSM_KEYBLOB_RAW_FORMAT_OCTET_STRING; key.header().AlgorithmId = algid; key.header().KeyClass = CSSM_KEYCLASS_SESSION_KEY; key.header().KeyUsage = usage; key.header().KeyAttr = 0; key.KeyData = CssmData(data, length); // unwrap it into the CSP (but keep it raw) UnwrapKey unwrap(Server::csp(), CSSM_ALGID_NONE); CssmKey unwrappedKey; CssmData descriptiveData; unwrap(key, KeySpec(CSSM_KEYUSE_ANY, CSSM_KEYATTR_RETURN_DATA | CSSM_KEYATTR_EXTRACTABLE), unwrappedKey, &descriptiveData, NULL); return CssmClient::Key(Server::csp(), unwrappedKey); }