gecko/services/crypto/WeaveCrypto.cpp

994 lines
28 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Weave code.
*
* The Initial Developer of the Original Code is
* Mozilla Corporation
* Portions created by the Initial Developer are Copyright (C) 2007
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Dan Mills <thunder@mozilla.com> (original author)
* Honza Bambas <honzab@allpeers.com>
* Justin Dolske <dolske@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "WeaveCrypto.h"
#include "nsStringAPI.h"
#include "nsAutoPtr.h"
#include "plbase64.h"
#include "prmem.h"
#include "secerr.h"
#include "pk11func.h"
#include "keyhi.h"
#include "nss.h"
NS_IMPL_ISUPPORTS1(WeaveCrypto, IWeaveCrypto)
WeaveCrypto::WeaveCrypto() :
mAlgorithm(SEC_OID_AES_256_CBC),
mKeypairBits(2048)
{
}
WeaveCrypto::~WeaveCrypto()
{
}
/*
* Base 64 encoding and decoding...
*/
nsresult
WeaveCrypto::EncodeBase64(const char *aData, PRUint32 aLength, nsACString& retval)
{
// Empty input? Nothing to do.
if (!aLength) {
retval.Assign(EmptyCString());
return NS_OK;
}
PRUint32 encodedLength = (aLength + 2) / 3 * 4;
char *encoded = (char *)PR_Malloc(encodedLength);
if (!encoded)
return NS_ERROR_OUT_OF_MEMORY;
PL_Base64Encode(aData, aLength, encoded);
retval.Assign(encoded, encodedLength);
PR_Free(encoded);
return NS_OK;
}
nsresult
WeaveCrypto::EncodeBase64(const nsACString& binary, nsACString& retval)
{
PromiseFlatCString fBinary(binary);
return EncodeBase64(fBinary.get(), fBinary.Length(), retval);
}
nsresult
WeaveCrypto::DecodeBase64(const nsACString& base64,
char *decoded, PRUint32 *decodedSize)
{
PromiseFlatCString fBase64(base64);
if (fBase64.Length() == 0) {
*decodedSize = 0;
return NS_OK;
}
// We expect at least 4 bytes of input
if (fBase64.Length() < 4)
return NS_ERROR_FAILURE;
PRUint32 size = (fBase64.Length() * 3) / 4;
// Adjust for padding.
if (*(fBase64.get() + fBase64.Length() - 1) == '=')
size--;
if (*(fBase64.get() + fBase64.Length() - 2) == '=')
size--;
// Just to be paranoid about buffer overflow.
if (*decodedSize < size)
return NS_ERROR_FAILURE;
*decodedSize = size;
if (!PL_Base64Decode(fBase64.get(), fBase64.Length(), decoded))
return NS_ERROR_ILLEGAL_VALUE;
return NS_OK;
}
nsresult
WeaveCrypto::DecodeBase64(const nsACString& base64, nsACString& retval)
{
PRUint32 decodedLength = base64.Length();
char *decoded = (char *)PR_Malloc(decodedLength);
if (!decoded)
return NS_ERROR_OUT_OF_MEMORY;
nsresult rv = DecodeBase64(base64, decoded, &decodedLength);
if (NS_FAILED(rv)) {
PR_Free(decoded);
return rv;
}
retval.Assign(decoded, decodedLength);
PR_Free(decoded);
return NS_OK;
}
//////////////////////////////////////////////////////////////////////////////
NS_IMETHODIMP
WeaveCrypto::GetAlgorithm(PRUint32 *aAlgorithm)
{
*aAlgorithm = mAlgorithm;
return NS_OK;
}
NS_IMETHODIMP
WeaveCrypto::SetAlgorithm(PRUint32 aAlgorithm)
{
mAlgorithm = (SECOidTag)aAlgorithm;
return NS_OK;
}
NS_IMETHODIMP
WeaveCrypto::GetKeypairBits(PRUint32 *aBits)
{
*aBits = mKeypairBits;
return NS_OK;
}
NS_IMETHODIMP
WeaveCrypto::SetKeypairBits(PRUint32 aBits)
{
mKeypairBits = aBits;
return NS_OK;
}
/*
* Encrypt
*/
NS_IMETHODIMP
WeaveCrypto::Encrypt(const nsACString& aClearText,
const nsACString& aSymmetricKey,
const nsACString& aIV,
nsACString& aCipherText)
{
nsresult rv = NS_OK;
// When using CBC padding, the output is 1 block larger than the input.
CK_MECHANISM_TYPE mech = PK11_AlgtagToMechanism(mAlgorithm);
PRUint32 blockSize = PK11_GetBlockSize(mech, nsnull);
PRUint32 outputBufferSize = aClearText.Length() + blockSize;
char *outputBuffer = (char *)PR_Malloc(outputBufferSize);
if (!outputBuffer)
return NS_ERROR_OUT_OF_MEMORY;
PromiseFlatCString input(aClearText);
rv = CommonCrypt(input.get(), input.Length(),
outputBuffer, &outputBufferSize,
aSymmetricKey, aIV, CKA_ENCRYPT);
if (NS_FAILED(rv))
goto encrypt_done;
rv = EncodeBase64(outputBuffer, outputBufferSize, aCipherText);
if (NS_FAILED(rv))
goto encrypt_done;
encrypt_done:
PR_Free(outputBuffer);
return rv;
}
/*
* Decrypt
*/
NS_IMETHODIMP
WeaveCrypto::Decrypt(const nsACString& aCipherText,
const nsACString& aSymmetricKey,
const nsACString& aIV,
nsACString& aClearText)
{
nsresult rv = NS_OK;
PRUint32 inputBufferSize = aCipherText.Length();
PRUint32 outputBufferSize = aCipherText.Length();
char *outputBuffer = (char *)PR_Malloc(outputBufferSize);
char *inputBuffer = (char *)PR_Malloc(inputBufferSize);
if (!inputBuffer || !outputBuffer)
return NS_ERROR_OUT_OF_MEMORY;
rv = DecodeBase64(aCipherText, inputBuffer, &inputBufferSize);
if (NS_FAILED(rv))
goto decrypt_done;
rv = CommonCrypt(inputBuffer, inputBufferSize,
outputBuffer, &outputBufferSize,
aSymmetricKey, aIV, CKA_DECRYPT);
if (NS_FAILED(rv))
goto decrypt_done;
aClearText.Assign(outputBuffer, outputBufferSize);
decrypt_done:
PR_Free(outputBuffer);
PR_Free(inputBuffer);
return rv;
}
/*
* CommonCrypt
*/
nsresult
WeaveCrypto::CommonCrypt(const char *input, PRUint32 inputSize,
char *output, PRUint32 *outputSize,
const nsACString& aSymmetricKey,
const nsACString& aIV,
CK_ATTRIBUTE_TYPE aOperation)
{
nsresult rv = NS_OK;
PK11SymKey *symKey = nsnull;
PK11Context *ctx = nsnull;
PK11SlotInfo *slot = nsnull;
SECItem *ivParam = nsnull;
PRUint32 maxOutputSize;
char keyData[aSymmetricKey.Length()];
PRUint32 keyDataSize = sizeof(keyData);
char ivData[aIV.Length()];
PRUint32 ivDataSize = sizeof(ivData);
rv = DecodeBase64(aSymmetricKey, keyData, &keyDataSize);
NS_ENSURE_SUCCESS(rv, rv);
rv = DecodeBase64(aIV, ivData, &ivDataSize);
NS_ENSURE_SUCCESS(rv, rv);
SECItem keyItem = {siBuffer, (unsigned char*)keyData, keyDataSize};
SECItem ivItem = {siBuffer, (unsigned char*)ivData, ivDataSize};
// AES_128_CBC --> CKM_AES_CBC --> CKM_AES_CBC_PAD
CK_MECHANISM_TYPE mechanism = PK11_AlgtagToMechanism(mAlgorithm);
mechanism = PK11_GetPadMechanism(mechanism);
if (mechanism == CKM_INVALID_MECHANISM) {
NS_WARNING("Unknown key mechanism");
rv = NS_ERROR_FAILURE;
goto crypt_done;
}
ivParam = PK11_ParamFromIV(mechanism, &ivItem);
if (!ivParam) {
NS_WARNING("Couldn't create IV param");
rv = NS_ERROR_FAILURE;
goto crypt_done;
}
slot = PK11_GetInternalKeySlot();
if (!slot) {
NS_WARNING("PK11_GetInternalKeySlot failed");
rv = NS_ERROR_FAILURE;
goto crypt_done;
}
symKey = PK11_ImportSymKey(slot, mechanism, PK11_OriginUnwrap, aOperation, &keyItem, NULL);
if (!symKey) {
NS_WARNING("PK11_ImportSymKey failed");
rv = NS_ERROR_FAILURE;
goto crypt_done;
}
ctx = PK11_CreateContextBySymKey(mechanism, aOperation, symKey, ivParam);
if (!ctx) {
NS_WARNING("PK11_CreateContextBySymKey failed");
rv = NS_ERROR_FAILURE;
goto crypt_done;
}
int tmpOutSize; // XXX retarded. why is an signed int suddenly needed?
maxOutputSize = *outputSize;
rv = PK11_CipherOp(ctx,
(unsigned char *)output, &tmpOutSize, maxOutputSize,
(unsigned char *)input, inputSize);
if (NS_FAILED(rv)) {
NS_WARNING("PK11_CipherOp failed");
rv = NS_ERROR_FAILURE;
goto crypt_done;
}
*outputSize = tmpOutSize;
output += tmpOutSize;
maxOutputSize -= tmpOutSize;
// PK11_DigestFinal sure sounds like the last step for *hashing*, but it
// just seems to be an odd name -- NSS uses this to finish the current
// cipher operation. You'd think it would be called PK11_CipherOpFinal...
PRUint32 tmpOutSize2; // XXX WTF? Now unsigned again? What kind of crazy API is this?
rv = PK11_DigestFinal(ctx, (unsigned char *)output, &tmpOutSize2, maxOutputSize);
if (NS_FAILED(rv)) {
NS_WARNING("PK11_DigestFinal failed");
rv = NS_ERROR_FAILURE;
goto crypt_done;
}
*outputSize += tmpOutSize2;
crypt_done:
if (ctx)
PK11_DestroyContext(ctx, PR_TRUE);
if (symKey)
PK11_FreeSymKey(symKey);
if (slot)
PK11_FreeSlot(slot);
if (ivParam)
SECITEM_FreeItem(ivParam, PR_TRUE);
return rv;
}
/*
* GenerateKeypair
*
* Generates an RSA key pair, encrypts (wraps) the private key with the
* supplied passphrase, and returns both to the caller.
*
* Based on code from:
* http://www.mozilla.org/projects/security/pki/nss/sample-code/sample1.html
*/
NS_IMETHODIMP
WeaveCrypto::GenerateKeypair(const nsACString& aPassphrase,
const nsACString& aSalt,
const nsACString& aIV,
nsACString& aEncodedPublicKey,
nsACString& aWrappedPrivateKey)
{
nsresult rv;
SECStatus s;
SECKEYPrivateKey *privKey = nsnull;
SECKEYPublicKey *pubKey = nsnull;
PK11SlotInfo *slot = nsnull;
PK11RSAGenParams rsaParams;
rsaParams.keySizeInBits = mKeypairBits; // 1024, 2048, etc.
rsaParams.pe = 65537; // public exponent.
slot = PK11_GetInternalKeySlot();
if (!slot) {
NS_WARNING("PK11_GetInternalKeySlot failed");
rv = NS_ERROR_FAILURE;
goto keygen_done;
}
// Generate the keypair.
// XXX isSensitive sets PK11_ATTR_SENSITIVE | PK11_ATTR_PRIVATE
// Might want to use PK11_GenerateKeyPairWithFlags and not set
// CKA_PRIVATE, since that may trigger a master password entry, which is
// kind of pointless for session objects...
privKey = PK11_GenerateKeyPair(slot,
CKM_RSA_PKCS_KEY_PAIR_GEN,
&rsaParams, &pubKey,
PR_FALSE, // isPerm
PR_TRUE, // isSensitive
nsnull); // wincx
if (!privKey) {
NS_WARNING("PK11_GenerateKeyPair failed");
rv = NS_ERROR_FAILURE;
goto keygen_done;
}
s = PK11_SetPrivateKeyNickname(privKey, "Weave User PrivKey");
if (s != SECSuccess) {
NS_WARNING("PK11_SetPrivateKeyNickname failed");
rv = NS_ERROR_FAILURE;
goto keygen_done;
}
rv = WrapPrivateKey(privKey, aPassphrase, aSalt, aIV, aWrappedPrivateKey);
if (NS_FAILED(rv)) {
NS_WARNING("WrapPrivateKey failed");
rv = NS_ERROR_FAILURE;
goto keygen_done;
}
rv = EncodePublicKey(pubKey, aEncodedPublicKey);
if (NS_FAILED(rv)) {
NS_WARNING("EncodePublicKey failed");
rv = NS_ERROR_FAILURE;
goto keygen_done;
}
keygen_done:
// Cleanup allocated resources.
if (pubKey)
SECKEY_DestroyPublicKey(pubKey);
if (privKey)
SECKEY_DestroyPrivateKey(privKey);
if (slot)
PK11_FreeSlot(slot);
return rv;
}
/*
* DeriveKeyFromPassphrase
*
* Creates a symmertic key from a passphrase, using PKCS#5.
*/
nsresult
WeaveCrypto::DeriveKeyFromPassphrase(const nsACString& aPassphrase,
const nsACString& aSalt,
PK11SymKey **aSymKey)
{
nsresult rv;
PromiseFlatCString fPass(aPassphrase);
SECItem passphrase = {siBuffer, (unsigned char *)fPass.get(), fPass.Length()};
char saltBytes[aSalt.Length()];
PRUint32 saltBytesLength = aSalt.Length();
rv = DecodeBase64(aSalt, saltBytes, &saltBytesLength);
NS_ENSURE_SUCCESS(rv, rv);
SECItem salt = {siBuffer, (unsigned char*)saltBytes, saltBytesLength};
// http://mxr.mozilla.org/seamonkey/source/security/nss/lib/pk11wrap/pk11pbe.c#1261
// Bug 436577 prevents us from just using SEC_OID_PKCS5_PBKDF2 here
SECOidTag pbeAlg = mAlgorithm;
SECOidTag cipherAlg = mAlgorithm; // ignored by callee when pbeAlg != a pkcs5 mech.
SECOidTag prfAlg = SEC_OID_HMAC_SHA1; // callee picks if SEC_OID_UNKNOWN, but only SHA1 is supported
PRInt32 keyLength = 0; // Callee will pick.
PRInt32 iterations = 4096; // PKCS#5 recommends at least 1000.
SECAlgorithmID *algid = PK11_CreatePBEV2AlgorithmID(pbeAlg, cipherAlg, prfAlg,
keyLength, iterations, &salt);
if (!algid)
return NS_ERROR_FAILURE;
PK11SlotInfo *slot = PK11_GetInternalSlot();
if (!slot)
return NS_ERROR_FAILURE;
*aSymKey = PK11_PBEKeyGen(slot, algid, &passphrase, PR_FALSE, nsnull);
SECOID_DestroyAlgorithmID(algid, PR_TRUE);
PK11_FreeSlot(slot);
return (*aSymKey ? NS_OK : NS_ERROR_FAILURE);
}
/*
* WrapPrivateKey
*
* Extract the private key by wrapping it (ie, converting it to a binary
* blob and encoding it with a symmetric key)
*
* See:
* http://www.mozilla.org/projects/security/pki/nss/tech-notes/tn5.html,
* "Symmetric Key Wrapping/Unwrapping of a Private Key"
*
* The NSS softtoken uses sftk_PackagePrivateKey() to prepare the private key
* for wrapping. For RSA, that's an ASN1 encoded PKCS1 object.
*/
nsresult
WeaveCrypto::WrapPrivateKey(SECKEYPrivateKey *aPrivateKey,
const nsACString& aPassphrase,
const nsACString& aSalt,
const nsACString& aIV,
nsACString& aWrappedPrivateKey)
{
nsresult rv;
SECStatus s;
PK11SymKey *pbeKey = nsnull;
// Convert our passphrase to a symkey and get the IV in the form we want.
rv = DeriveKeyFromPassphrase(aPassphrase, aSalt, &pbeKey);
NS_ENSURE_SUCCESS(rv, rv);
char ivData[aIV.Length()];
PRUint32 ivDataSize = sizeof(ivData);
rv = DecodeBase64(aIV, ivData, &ivDataSize);
NS_ENSURE_SUCCESS(rv, rv);
SECItem ivItem = {siBuffer, (unsigned char*)ivData, ivDataSize};
// AES_128_CBC --> CKM_AES_CBC --> CKM_AES_CBC_PAD
CK_MECHANISM_TYPE wrapMech = PK11_AlgtagToMechanism(mAlgorithm);
wrapMech = PK11_GetPadMechanism(wrapMech);
if (wrapMech == CKM_INVALID_MECHANISM) {
NS_WARNING("Unknown key mechanism");
return NS_ERROR_FAILURE;
}
SECItem *ivParam = PK11_ParamFromIV(wrapMech, &ivItem);
if (!ivParam) {
NS_WARNING("Couldn't create IV param");
return NS_ERROR_FAILURE;
}
// Use a stack buffer to hold the wrapped key. NSS says about 1200 bytes for
// a 2048-bit RSA key, so our 4096 byte buffer should be plenty.
unsigned char stackBuffer[4096];
SECItem wrappedKey = {siBuffer, stackBuffer, sizeof(stackBuffer)};
s = PK11_WrapPrivKey(aPrivateKey->pkcs11Slot,
pbeKey, aPrivateKey,
wrapMech, ivParam,
&wrappedKey, nsnull);
SECITEM_FreeItem(ivParam, PR_TRUE);
PK11_FreeSymKey(pbeKey);
if (s != SECSuccess) {
NS_WARNING("PK11_WrapPrivKey failed");
return(NS_ERROR_FAILURE);
}
rv = EncodeBase64((char *)wrappedKey.data, wrappedKey.len, aWrappedPrivateKey);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
/*
* EncodePublicKey
*
* http://svn.mozilla.org/projects/mccoy/trunk/components/src/KeyPair.cpp
*/
nsresult
WeaveCrypto::EncodePublicKey(SECKEYPublicKey *aPublicKey,
nsACString& aEncodedPublicKey)
{
//SECItem *derKey = PK11_DEREncodePublicKey(aPublicKey);
SECItem *derKey = SECKEY_EncodeDERSubjectPublicKeyInfo(aPublicKey);
if (!derKey)
return NS_ERROR_FAILURE;
nsresult rv = EncodeBase64((char *)derKey->data, derKey->len, aEncodedPublicKey);
NS_ENSURE_SUCCESS(rv, rv);
// XXX destroy derKey?
return NS_OK;
}
/*
* GenerateRandomBytes
*/
NS_IMETHODIMP
WeaveCrypto::GenerateRandomBytes(PRUint32 aByteCount,
nsACString& aEncodedBytes)
{
nsresult rv;
char random[aByteCount];
rv = PK11_GenerateRandom((unsigned char *)random, aByteCount);
NS_ENSURE_SUCCESS(rv, rv);
rv = EncodeBase64(random, aByteCount, aEncodedBytes);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
/*
* GenerateRandomIV
*/
NS_IMETHODIMP
WeaveCrypto::GenerateRandomIV(nsACString& aEncodedBytes)
{
nsresult rv;
CK_MECHANISM_TYPE mech = PK11_AlgtagToMechanism(mAlgorithm);
PRUint32 size = PK11_GetIVLength(mech);
char random[size];
rv = PK11_GenerateRandom((unsigned char *)random, size);
NS_ENSURE_SUCCESS(rv, rv);
rv = EncodeBase64(random, size, aEncodedBytes);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
/*
* GenerateRandomKey
*
* Generates a random symmetric key, and returns it in base64 encoded form.
*
* Note that we could just use GenerateRandomBytes to do all this (at least
* for AES), but ideally we'd be able to switch to passing around key handles
* insead of the actual key bits, which would require generating an actual
* key.
*/
NS_IMETHODIMP
WeaveCrypto::GenerateRandomKey(nsACString& aEncodedKey)
{
nsresult rv = NS_OK;
PRUint32 keySize;
CK_MECHANISM_TYPE keygenMech;
SECItem *keydata = nsnull;
// XXX doesn't NSS have a lookup function to do this?
switch (mAlgorithm) {
case AES_128_CBC:
keygenMech = CKM_AES_KEY_GEN;
keySize = 16;
break;
case AES_192_CBC:
keygenMech = CKM_AES_KEY_GEN;
keySize = 24;
break;
case AES_256_CBC:
keygenMech = CKM_AES_KEY_GEN;
keySize = 32;
break;
default:
NS_WARNING("Unknown random keygen algorithm");
return NS_ERROR_FAILURE;
}
PK11SlotInfo *slot = PK11_GetInternalSlot();
if (!slot)
return NS_ERROR_FAILURE;
PK11SymKey* randKey = PK11_KeyGen(slot, keygenMech, NULL, keySize, NULL);
if (!randKey) {
NS_WARNING("PK11_KeyGen failed");
rv = NS_ERROR_FAILURE;
goto keygen_done;
}
if (PK11_ExtractKeyValue(randKey)) {
NS_WARNING("PK11_ExtractKeyValue failed");
rv = NS_ERROR_FAILURE;
goto keygen_done;
}
keydata = PK11_GetKeyData(randKey);
if (!keydata) {
NS_WARNING("PK11_GetKeyData failed");
rv = NS_ERROR_FAILURE;
goto keygen_done;
}
rv = EncodeBase64((char *)keydata->data, keydata->len, aEncodedKey);
if (NS_FAILED(rv)) {
NS_WARNING("EncodeBase64 failed");
goto keygen_done;
}
keygen_done:
// XXX does keydata need freed?
if (randKey)
PK11_FreeSymKey(randKey);
if (slot)
PK11_FreeSlot(slot);
return rv;
}
/*
* WrapSymmetricKey
*/
NS_IMETHODIMP
WeaveCrypto::WrapSymmetricKey(const nsACString& aSymmetricKey,
const nsACString& aPublicKey,
nsACString& aWrappedKey)
{
nsresult rv = NS_OK;
SECStatus s;
PK11SlotInfo *slot = nsnull;
PK11SymKey *symKey = nsnull;
SECKEYPublicKey *pubKey = nsnull;
CERTSubjectPublicKeyInfo *pubKeyInfo = nsnull;
CK_MECHANISM_TYPE keyMech, wrapMech;
// Step 1. Get rid of the base64 encoding on the inputs.
char publicKeyBuffer[aPublicKey.Length()];
PRUint32 publicKeyBufferSize = aPublicKey.Length();
rv = DecodeBase64(aPublicKey, publicKeyBuffer, &publicKeyBufferSize);
NS_ENSURE_SUCCESS(rv, rv);
SECItem pubKeyData = {siBuffer, (unsigned char *)publicKeyBuffer, publicKeyBufferSize};
char symKeyBuffer[aSymmetricKey.Length()];
PRUint32 symKeyBufferSize = aSymmetricKey.Length();
rv = DecodeBase64(aSymmetricKey, symKeyBuffer, &symKeyBufferSize);
NS_ENSURE_SUCCESS(rv, rv);
SECItem symKeyData = {siBuffer, (unsigned char *)symKeyBuffer, symKeyBufferSize};
char wrappedBuffer[4096];
SECItem wrappedKey = {siBuffer, (unsigned char *)wrappedBuffer, sizeof(wrappedBuffer)};
// Step 2. Put the symmetric key bits into a P11 key object.
slot = PK11_GetInternalSlot();
if (!slot) {
NS_WARNING("Can't get internal PK11 slot");
rv = NS_ERROR_FAILURE;
goto wrap_done;
}
// ImportSymKey wants a mechanism, from which it derives the key type.
keyMech = PK11_AlgtagToMechanism(mAlgorithm);
if (keyMech == CKM_INVALID_MECHANISM) {
NS_WARNING("Unknown key mechanism");
rv = NS_ERROR_FAILURE;
goto wrap_done;
}
// This imports a key with the usage set for encryption, but that doesn't
// really matter because we're just going to wrap it up and not use it.
symKey = PK11_ImportSymKey(slot,
keyMech,
PK11_OriginUnwrap,
CKA_ENCRYPT,
&symKeyData,
NULL);
if (!symKey) {
NS_WARNING("PK11_ImportSymKey failed");
rv = NS_ERROR_FAILURE;
goto wrap_done;
}
// Step 3. Put the public key bits into a P11 key object.
// Can't just do this directly, it's expecting a minimal ASN1 blob
// pubKey = SECKEY_ImportDERPublicKey(&pubKeyData, CKK_RSA);
pubKeyInfo = SECKEY_DecodeDERSubjectPublicKeyInfo(&pubKeyData);
if (!pubKeyInfo) {
NS_WARNING("SECKEY_DecodeDERSubjectPublicKeyInfo failed");
rv = NS_ERROR_FAILURE;
goto wrap_done;
}
pubKey = SECKEY_ExtractPublicKey(pubKeyInfo);
if (!pubKey) {
NS_WARNING("SECKEY_ExtractPublicKey failed");
rv = NS_ERROR_FAILURE;
goto wrap_done;
}
// Step 4. Wrap the symmetric key with the public key.
wrapMech = PK11_AlgtagToMechanism(SEC_OID_PKCS1_RSA_ENCRYPTION);
s = PK11_PubWrapSymKey(wrapMech, pubKey, symKey, &wrappedKey);
if (s != SECSuccess) {
NS_WARNING("PK11_PubWrapSymKey failed");
rv = NS_ERROR_FAILURE;
goto wrap_done;
}
// Step 5. Base64 encode the wrapped key, cleanup, and return to caller.
rv = EncodeBase64((char *)wrappedKey.data, wrappedKey.len, aWrappedKey);
if (NS_FAILED(rv)) {
NS_WARNING("EncodeBase64 failed");
goto wrap_done;
}
wrap_done:
if (pubKey)
SECKEY_DestroyPublicKey(pubKey);
if (pubKeyInfo)
SECKEY_DestroySubjectPublicKeyInfo(pubKeyInfo);
if (symKey)
PK11_FreeSymKey(symKey);
if (slot)
PK11_FreeSlot(slot);
return rv;
}
/*
* UnwrapSymmetricKey
*/
NS_IMETHODIMP
WeaveCrypto::UnwrapSymmetricKey(const nsACString& aWrappedSymmetricKey,
const nsACString& aWrappedPrivateKey,
const nsACString& aPassphrase,
const nsACString& aSalt,
const nsACString& aIV,
nsACString& aSymmetricKey)
{
nsresult rv = NS_OK;
PK11SlotInfo *slot = nsnull;
PK11SymKey *pbeKey = nsnull;
PK11SymKey *symKey = nsnull;
SECKEYPrivateKey *privKey = nsnull;
SECItem *ivParam = nsnull;
SECItem *symKeyData = nsnull;
SECItem *keyID = nsnull;
CK_ATTRIBUTE_TYPE privKeyUsage[] = { CKA_UNWRAP };
PRUint32 privKeyUsageLength = sizeof(privKeyUsage) / sizeof(CK_ATTRIBUTE_TYPE);
// Step 1. Get rid of the base64 encoding on the inputs.
char privateKeyBuffer[aWrappedPrivateKey.Length()];
PRUint32 privateKeyBufferSize = aWrappedPrivateKey.Length();
rv = DecodeBase64(aWrappedPrivateKey, privateKeyBuffer, &privateKeyBufferSize);
NS_ENSURE_SUCCESS(rv, rv);
SECItem wrappedPrivKey = {siBuffer, (unsigned char *)privateKeyBuffer, privateKeyBufferSize};
char wrappedKeyBuffer[aWrappedSymmetricKey.Length()];
PRUint32 wrappedKeyBufferSize = aWrappedSymmetricKey.Length();
rv = DecodeBase64(aWrappedSymmetricKey, wrappedKeyBuffer, &wrappedKeyBufferSize);
NS_ENSURE_SUCCESS(rv, rv);
SECItem wrappedSymKey = {siBuffer, (unsigned char *)wrappedKeyBuffer, wrappedKeyBufferSize};
// Step 2. Convert the passphrase to a symmetric key and get the IV in the proper form.
rv = DeriveKeyFromPassphrase(aPassphrase, aSalt, &pbeKey);
NS_ENSURE_SUCCESS(rv, rv);
char ivData[aIV.Length()];
PRUint32 ivDataSize = sizeof(ivData);
rv = DecodeBase64(aIV, ivData, &ivDataSize);
NS_ENSURE_SUCCESS(rv, rv);
SECItem ivItem = {siBuffer, (unsigned char*)ivData, ivDataSize};
// AES_128_CBC --> CKM_AES_CBC --> CKM_AES_CBC_PAD
CK_MECHANISM_TYPE wrapMech = PK11_AlgtagToMechanism(mAlgorithm);
wrapMech = PK11_GetPadMechanism(wrapMech);
if (wrapMech == CKM_INVALID_MECHANISM) {
NS_WARNING("Unknown key mechanism");
rv = NS_ERROR_FAILURE;
goto unwrap_done;
}
ivParam = PK11_ParamFromIV(wrapMech, &ivItem);
if (!ivParam) {
NS_WARNING("Couldn't create IV param");
rv = NS_ERROR_FAILURE;
goto unwrap_done;
}
// Step 3. Unwrap the private key with the key from the passphrase.
slot = PK11_GetInternalSlot();
if (!slot) {
NS_WARNING("Can't get internal PK11 slot");
rv = NS_ERROR_FAILURE;
goto unwrap_done;
}
// Normally, one wants to associate a private key with a public key.
// P11_UnwrapPrivKey() passes its keyID arg to PK11_MakeIDFromPubKey(),
// which hashes the public key to create an ID (or, for small inputs,
// assumes it's already hashed and does nothing).
// We don't really care about this, because our unwrapped private key will
// just live long enough to unwrap the bulk data key. So, we'll just jam in
// a random value... We have an IV handy, so that will suffice.
keyID = &ivItem;
privKey = PK11_UnwrapPrivKey(slot,
pbeKey, wrapMech, ivParam, &wrappedPrivKey,
nsnull, // label (SECItem)
keyID,
PR_FALSE, // isPerm (token object)
PR_TRUE, // isSensitive
CKK_RSA,
privKeyUsage, privKeyUsageLength,
nsnull); // wincx
if (!privKey) {
NS_WARNING("PK11_UnwrapPrivKey failed");
rv = NS_ERROR_FAILURE;
goto unwrap_done;
}
// Step 4. Unwrap the symmetric key with the user's private key.
// XXX also have PK11_PubUnwrapSymKeyWithFlags() if more control is needed.
// (last arg is keySize, 0 seems to work)
symKey = PK11_PubUnwrapSymKey(privKey, &wrappedSymKey, wrapMech,
CKA_DECRYPT, 0);
if (!symKey) {
NS_WARNING("PK11_PubUnwrapSymKey failed");
rv = NS_ERROR_FAILURE;
goto unwrap_done;
}
// Step 5. Base64 encode the unwrapped key, cleanup, and return to caller.
if (PK11_ExtractKeyValue(symKey)) {
NS_WARNING("PK11_ExtractKeyValue failed");
rv = NS_ERROR_FAILURE;
goto unwrap_done;
}
// XXX need to free this?
symKeyData = PK11_GetKeyData(symKey);
if (!symKeyData) {
NS_WARNING("PK11_GetKeyData failed");
rv = NS_ERROR_FAILURE;
goto unwrap_done;
}
rv = EncodeBase64((char *)symKeyData->data, symKeyData->len, aSymmetricKey);
if (NS_FAILED(rv)) {
NS_WARNING("EncodeBase64 failed");
goto unwrap_done;
}
unwrap_done:
if (privKey)
SECKEY_DestroyPrivateKey(privKey);
if (symKey)
PK11_FreeSymKey(symKey);
if (pbeKey)
PK11_FreeSymKey(pbeKey);
if (slot)
PK11_FreeSlot(slot);
if (ivParam)
SECITEM_FreeItem(ivParam, PR_TRUE);
return rv;
}