//
// Encode a key into a blob.
// We'll have to ask our Database to do this - we don't have its keys.
// Note that this returns memory we own and keep.
//
KeyBlob *KeychainKey::blob()
{
	if (!mValidBlob) {
        assert(mValidKey);		// must have valid key to encode

        // export Key ACL to blob form
        CssmData pubAcl, privAcl;
		acl().exportBlob(pubAcl, privAcl);
        
        // assemble external key form
        CssmKey externalKey = mKey;
		externalKey.clearAttribute(forcedAttributes);
        externalKey.setAttribute(mAttributes);

        // encode the key and replace blob
        KeyBlob *newBlob = database().encodeKey(externalKey, pubAcl, privAcl);
        Allocator::standard().free(mBlob);
        mBlob = newBlob;
        mValidBlob = true;
    
        // clean up and go
        acl().allocator.free(pubAcl);
        acl().allocator.free(privAcl);
	}
	return mBlob;
}
//
// Decode a key blob
//
void DatabaseCryptoCore::decodeKeyCore(KeyBlob *blob,
    CssmKey &key, void * &pubAcl, void * &privAcl) const
{    
    // 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());
	
	bool inTheClear = blob->isClearText();
	if(!inTheClear) {
		// verify signature (check against corruption)
		assert(isValid());		// need our database secrets
		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);
    }
	/* else signature indicates cleartext */
	
    // extract and hold some header bits the CSP does not want to see
    uint32 heldAttributes = n2h(blob->header.attributes()) & managedAttributes;
   
	CssmData privAclData;
	if(inTheClear) {
		/* NULL unwrap */
		UnwrapKey unwrap(Server::csp(), CSSM_ALGID_NONE);
		wrappedKey.clearAttribute(managedAttributes);    //@@@ shouldn't be needed(?)
		unwrap(wrappedKey,
			KeySpec(n2h(blob->header.usage()),
				(n2h(blob->header.attributes()) & ~managedAttributes) | forcedAttributes),
			key, &privAclData);
	}
	else {
		// 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));
		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;
    
	if(inTheClear && (real.keyClass() != CSSM_KEYCLASS_PUBLIC_KEY)) {
		/* Spoof - cleartext KeyBlob passed off as private key */
        CssmError::throwMe(CSSMERR_CSP_INVALID_KEY);
	}
	
    // got a valid key: return the pieces
    pubAcl = blob->publicAclBlob();		// points into blob (shared)
    privAcl = privAclData;				// was allocated by CSP decrypt, else NULL for
										// cleatext keys
    // key was set by unwrap operation
}
//
// Encode a key blob
//
KeyBlob *DatabaseCryptoCore::encodeKeyCore(const CssmKey &inKey,
    const CssmData &publicAcl, const CssmData &privateAcl,
	bool inTheClear) const
{
    CssmKey key = inKey;
	uint8 iv[8];
	CssmKey wrappedKey;

	if(inTheClear && (privateAcl.Length != 0)) {
		/* can't store private ACL component in the clear */
		CssmError::throwMe(CSSMERR_DL_INVALID_ACCESS_CREDENTIALS);
	}
	
    // extract and hold some header bits the CSP does not want to see
    uint32 heldAttributes = key.attributes() & managedAttributes;
    key.clearAttribute(managedAttributes);
	key.setAttribute(forcedAttributes);
    
	if(inTheClear) {
		/* NULL wrap of public key */
		WrapKey wrap(Server::csp(), CSSM_ALGID_NONE);
		wrap(key, wrappedKey, NULL);
	}
	else {
		assert(isValid());		// need our database secrets
		
		// create new IV
		Server::active().random(iv);
		
	   // 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));
		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<KeyBlob>(length);
    
    // assemble the KeyBlob
    memset(blob, 0, sizeof(KeyBlob));	// fill alignment gaps
    blob->initialize();
	if(!inTheClear) {
		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();
    
 	if(inTheClear) {
		/* indicate that this is cleartext for decoding */
		blob->setClearTextSignature();
	}
	else {
		// 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;
}