]> git.saurik.com Git - apple/security.git/blobdiff - libsecurity_apple_csp/lib/pkcs12Derive.cpp
Security-55163.44.tar.gz
[apple/security.git] / libsecurity_apple_csp / lib / pkcs12Derive.cpp
diff --git a/libsecurity_apple_csp/lib/pkcs12Derive.cpp b/libsecurity_apple_csp/lib/pkcs12Derive.cpp
new file mode 100644 (file)
index 0000000..5fe8d64
--- /dev/null
@@ -0,0 +1,447 @@
+/*
+ * Copyright (c) 2003 Apple Computer, Inc. All Rights Reserved.
+ * 
+ * The contents of this file constitute Original Code as defined in and are
+ * subject to the Apple Public Source License Version 1.2 (the 'License').
+ * You may not use this file except in compliance with the License. Please 
+ * obtain a copy of the License at http://www.apple.com/publicsource and 
+ * read it before using this file.
+ * 
+ * This 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.
+ */
+/*
+ * pkcs12Derive.cpp - PKCS12 PBE routine
+ *
+ * Created 2/28/03 by Doug Mitchell.
+ */
+
+#include <Security/cssmapple.h>
+#include <openssl/bn.h>
+#include <pbkdDigest.h>
+
+#include "pkcs12Derive.h"
+#include "AppleCSPUtils.h"
+#include "AppleCSPContext.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <assert.h>
+#include <security_asn1/SecNssCoder.h>
+
+#include <CoreFoundation/CoreFoundation.h>
+
+/* specify which flavor of bits to generate */
+typedef enum {
+       PBE_ID_Key      = 1,
+       PBE_ID_IV       = 2,
+       PBE_ID_MAC      = 3
+} P12_PBE_ID;  
+  
+/*
+ * Create a "string" (in the loose p12 notation) of specified length 
+ * from the concatention of copies of the specified input string.
+ */
+static unsigned char *p12StrCat(
+       const unsigned char *inStr,
+       unsigned inStrLen,
+       SecNssCoder &coder,
+       unsigned outLen,
+       unsigned char *outStr = NULL)   // if not present, we malloc
+{
+       if(outStr == NULL) {
+               outStr = (unsigned char *)coder.malloc(outLen);
+       }
+       unsigned toMove = outLen;
+       unsigned char *outp = outStr;
+       while(toMove) {
+               unsigned thisMove = inStrLen;
+               if(thisMove > toMove) {
+                       thisMove = toMove;
+               }
+               memmove(outp, inStr, thisMove);
+               toMove -= thisMove;
+               outp   += thisMove;
+       }
+       return outStr;
+}
+
+/*
+ * PBE generator per PKCS12 v.1 section B.2.
+ */
+static CSSM_RETURN p12PbeGen(
+       const CSSM_DATA         &pwd,                   // unicode, double null terminated
+       const uint8             *salt,
+       unsigned                        saltLen,
+       unsigned                        iterCount,
+       P12_PBE_ID                      pbeId,
+       CSSM_ALGORITHMS         hashAlg,                // MS5 or SHA1 only
+       SecNssCoder                     &coder,                 // for temp allocs
+       /* result goes here, mallocd by caller */
+       uint8                           *outbuf,        
+       unsigned                        outbufLen)
+{
+       CSSM_RETURN ourRtn = CSSM_OK;
+       unsigned unipassLen = pwd.Length;
+       unsigned char *unipass = pwd.Data;
+       int irtn;
+       
+       /*
+        * all variables of the form p12_<XXX> represent <XXX> from the 
+        * PKCS12 spec. E.g., p12_u is u, the length of the digest output.
+        * Only difference here is: all of our sizes are in BYTES, not
+        * bits. 
+        */
+       unsigned p12_r = iterCount;
+       unsigned p12_n = outbufLen;
+
+       unsigned p12_u;                                 // hash output size
+       unsigned p12_v;                                 // hash block size
+       unsigned char *p12_P = NULL;    // catted passwords
+       unsigned char *p12_S = NULL;    // catted salts
+       
+       switch(hashAlg) {
+               case CSSM_ALGID_MD5:
+                       p12_u = kMD5DigestSize;
+                       p12_v = kMD5BlockSize;
+                       break;
+               case CSSM_ALGID_SHA1:
+                       p12_u = kSHA1DigestSize;
+                       p12_v = kSHA1BlockSize;
+                       break;
+               default:
+                       return CSSMERR_CSP_INVALID_ALGORITHM;
+       }
+       
+       /* 
+        * 1. Construct a string, D (the diversifier), by 
+        *    concatenating v/8 copies of ID.
+        */
+       unsigned char *p12_D = NULL;            // diversifier
+       p12_D = (unsigned char *)coder.malloc(p12_v);
+       for(unsigned dex=0; dex<p12_v; dex++) {
+               p12_D[dex] = (unsigned char)pbeId;
+       }
+       
+       /*
+        * 2. Concatenate copies of the salt together to create 
+        *    a string S of length v * ceil(s/v) bits (the final copy 
+        *    of the salt may be truncated to create S). Note that if 
+        *    the salt is the empty string, then so is S.
+        */
+       unsigned p12_Slen = p12_v * ((saltLen + p12_v - 1) / p12_v);
+       if(p12_Slen) {
+               p12_S = p12StrCat(salt, saltLen, coder, p12_Slen);
+       }
+       
+        
+       /* 
+        * 3. Concatenate copies of the password together to create 
+        *    a string P of length v * ceil(p/v) bits (the final copy of 
+        *    the password may be truncated to create P). Note that 
+        *    if the password is the empty string, then so is P.
+        */
+       unsigned p12_Plen = p12_v * ((unipassLen + p12_v - 1) / p12_v);
+       if(p12_Plen) {
+               p12_P = p12StrCat(unipass, unipassLen, coder, p12_Plen);
+       }
+       
+       /*
+        * 4. Set I= S||P to be the concatenation of S and P.
+        */
+       unsigned char *p12_I = 
+               (unsigned char *)coder.malloc(p12_Slen + p12_Plen);
+       memmove(p12_I, p12_S, p12_Slen);
+       if(p12_Plen) {
+               memmove(p12_I + p12_Slen, p12_P, p12_Plen);
+       }
+       
+       /*
+        * 5. Set c = ceil(n/u). 
+        */
+       unsigned p12_c = (p12_n + p12_u - 1) / p12_u;
+       
+       /* allocate c hash-output-size bufs */
+       unsigned char *p12_A = (unsigned char *)coder.malloc(p12_c * p12_u);
+
+       /* one reusable hash object */
+       DigestCtx ourDigest;
+       DigestCtx *hashHand = &ourDigest;
+       memset(hashHand, 0, sizeof(hashHand));
+       
+       /* reused inside the loop */
+       unsigned char *p12_B = (unsigned char *)coder.malloc(p12_v + 1);
+       BIGNUM *Ij = BN_new();
+       BIGNUM *Bpl1 = BN_new();
+       
+       /*
+        * 6. For i=1, 2, ..., p12_c, do the following:
+        */
+       for(unsigned p12_i=0; p12_i<p12_c; p12_i++) {
+               unsigned char *p12_AsubI = p12_A + (p12_i * p12_u);
+               
+               /* 
+                * a) Set A[i] = H**r(D||I). (i.e. the rth hash of D||I, 
+                *    H(H(H(...H(D||I))))
+                */
+               irtn = DigestCtxInit(hashHand, hashAlg); 
+               if(!irtn) {
+                       ourRtn = CSSMERR_CSP_INTERNAL_ERROR;
+                       break;
+               }
+               DigestCtxUpdate(hashHand, p12_D, p12_v); 
+               DigestCtxUpdate(hashHand, p12_I, p12_Slen + p12_Plen);
+               DigestCtxFinal(hashHand, p12_AsubI); 
+
+               for(unsigned iter=1; iter<p12_r; iter++) {
+                       irtn = DigestCtxInit(hashHand, hashAlg);
+                       if(!irtn) {
+                               ourRtn = CSSMERR_CSP_INTERNAL_ERROR;
+                               break;
+                       }
+                       DigestCtxUpdate(hashHand, p12_AsubI, p12_u);
+                       DigestCtxFinal(hashHand, p12_AsubI);
+               }
+               
+               /*
+                * b) Concatenate copies of A[i] to create a string B of 
+                *    length v bits (the final copy of A[i]i may be truncated 
+                *    to create B).
+                */
+               p12StrCat(p12_AsubI, p12_u, coder, p12_v, p12_B);
+               
+               /*
+                * c) Treating I as a concatenation I[0], I[1], ..., 
+                *    I[k-1] of v-bit blocks, where k = ceil(s/v) + ceil(p/v),
+                *    modify I by setting I[j]=(I[j]+B+1) mod (2 ** v)
+                *    for each j.
+                *
+                * Copied from PKCS12_key_gen_uni() from openssl...
+                */
+               /* Work out B + 1 first then can use B as tmp space */
+               BN_bin2bn (p12_B, p12_v, Bpl1);
+               BN_add_word (Bpl1, 1);
+               unsigned Ilen = p12_Slen + p12_Plen;
+               
+               for (unsigned j = 0; j < Ilen; j+=p12_v) {
+                       BN_bin2bn (p12_I + j, p12_v, Ij);
+                       BN_add (Ij, Ij, Bpl1);
+                       BN_bn2bin (Ij, p12_B);
+                       unsigned Ijlen = BN_num_bytes (Ij);
+                       /* If more than 2^(v*8) - 1 cut off MSB */
+                       if (Ijlen > p12_v) {
+                               BN_bn2bin (Ij, p12_B);
+                               memcpy (p12_I + j, p12_B + 1, p12_v);
+                       /* If less than v bytes pad with zeroes */
+                       } else if (Ijlen < p12_v) {
+                               memset(p12_I + j, 0, p12_v - Ijlen);
+                               BN_bn2bin(Ij, p12_I + j + p12_v - Ijlen); 
+                       } else BN_bn2bin (Ij, p12_I + j);
+               }
+       }       
+        
+       if(ourRtn == CSSM_OK) { 
+               /*
+                * 7. Concatenate A[1], A[2], ..., A[c] together to form a 
+                *    pseudo-random bit string, A.
+                *
+                * 8. Use the first n bits of A as the output of this entire 
+                *    process.
+                */
+               memmove(outbuf, p12_A, outbufLen);
+       }
+       
+       /* clear all these strings */
+       if(p12_D) {
+               memset(p12_D, 0, p12_v);
+       }
+       if(p12_S) {
+               memset(p12_S, 0, p12_Slen);
+       }
+       if(p12_P) {
+               memset(p12_P, 0, p12_Plen);
+       }
+       if(p12_I) {
+               memset(p12_I, 0, p12_Slen + p12_Plen);
+       }
+       if(p12_A) {
+               memset(p12_A, 0, p12_c * p12_u);
+       }
+       if(p12_B) {
+               memset(p12_B, 0, p12_v);
+       }
+       if(hashHand) {
+               DigestCtxFree(hashHand);
+       }
+       BN_free(Bpl1);
+       BN_free(Ij);
+       return ourRtn;
+}
+
+/*
+ * Public P12 derive key function, called out from 
+ * AppleCSPSession::DeriveKey()
+ *
+ * On input:
+ * ---------
+ * Context parameters:
+ *     Salt
+ *             Iteration Count
+ *             CSSM_CRYPTO_DATA.Param - Unicode passphrase, double-NULL terminated
+ *             Algorithm - CSSM_ALGID_PKCS12_PBE_{ENCR,MAC}
+ * Passed explicitly from DeriveKey():
+ *             CSSM_DATA Param - IN/OUT - optional IV - caller mallocs space to
+ *                     tell us to generate an IV. The param itself is not 
+ *                     optional; the presence or absence of allocated data in it
+ *                     is our IV indicator (present/absent as well as size)
+ *             KeyData - mallocd by caller, we fill in keyData->Length bytes
+ */
+void DeriveKey_PKCS12 (
+       const Context &context,
+       AppleCSPSession &session,
+       const CssmData &Param,                  // other's public key
+       CSSM_DATA *keyData)                             // mallocd by caller
+                                                                       // we fill in keyData->Length bytes
+{
+       SecNssCoder tmpCoder;
+       
+       /*
+        * According to the spec, both passphrase and salt are optional.
+        * Get them from context if they're present. In practical terms
+        * the user really should supply a passphrase either in the 
+        * seed attribute (as a Unicode passphrase) or as the BaseKey
+        * as a CSSM_ALGID_SECURE_PASSPHRASE key). 
+        */
+       CSSM_DATA pwd = {0, NULL};
+       CSSM_DATA appPwd = {0, NULL};
+       CssmCryptoData *cryptData = 
+               context.get<CssmCryptoData>(CSSM_ATTRIBUTE_SEED);
+       if((cryptData != NULL) && (cryptData->Param.Length != 0)) {
+               appPwd = cryptData->Param;
+       }
+       else {
+               /* Get pwd from base key */
+               CssmKey *passKey = context.get<CssmKey>(CSSM_ATTRIBUTE_KEY);
+               if (passKey != NULL) {
+                       AppleCSPContext::symmetricKeyBits(context, session,
+                               CSSM_ALGID_SECURE_PASSPHRASE, CSSM_KEYUSE_DERIVE, 
+                               appPwd.Data, appPwd.Length);
+               }
+       }
+       if(appPwd.Data) {
+               /*
+                * The incoming passphrase is a UTF8 encoded enternal representation
+                * of a CFString. Convert to CFString and obtain the unicode characters
+                * from the string.
+                */
+               CFDataRef cfData = CFDataCreate(NULL, appPwd.Data, appPwd.Length);
+               CFStringRef cfStr = CFStringCreateFromExternalRepresentation(NULL,
+                       cfData, kCFStringEncodingUTF8);
+        if (cfData)
+            CFRelease(cfData);
+               if(cfStr == NULL) {
+                       CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_SEED);
+               }
+               
+               /* convert unicode to chars with an extra double-NULL */
+               unsigned len = CFStringGetLength(cfStr);
+               tmpCoder.allocItem(pwd, sizeof(UniChar) * (len + 1));
+               unsigned char *cp = pwd.Data;
+               UniChar uc = 0;
+               for(unsigned dex=0; dex<len; dex++) {
+                       uc = CFStringGetCharacterAtIndex(cfStr, dex);
+                       *cp++ = uc >> 8;
+                       *cp++ = uc & 0xff;
+               }
+               /* CFString tends to include a NULL at the end; add it if it's not there */
+               if(uc == 0) {
+                       if(pwd.Length < 2) {
+                               CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_SEED);
+                       }
+                       pwd.Length -= 2;
+               }
+               else {
+                       *cp++ = 0;
+                       *cp++ = 0;
+               }
+        if (cfStr)
+            CFRelease(cfStr);
+       }
+
+       /* salt from context */
+       uint32 saltLen = 0;
+       uint8  *salt = NULL;
+       CssmData *csalt = context.get<CssmData>(CSSM_ATTRIBUTE_SALT);
+       if(csalt) {
+               salt = csalt->Data;
+               saltLen = csalt->Length;
+       }
+       
+       /* 
+        * Iteration count, from context, required.
+        * The spec's ASN1 definition says this is optional with a default
+        * of one but that's a BER encode/decode issue. Here we require
+        * a nonzero value.
+        */
+       uint32 iterCount = context.getInt(CSSM_ATTRIBUTE_ITERATION_COUNT,
+               CSSMERR_CSP_MISSING_ATTR_ITERATION_COUNT);
+       if(iterCount == 0) {
+               CssmError::throwMe(CSSMERR_CSP_INVALID_ATTR_ITERATION_COUNT);
+       }
+
+       /* 
+        * Algorithm determines which of {PBE_ID_Key,PBE_ID_MAC} we now
+        * generate. We'll also do an optional PBE_ID_IV later.
+        */
+       P12_PBE_ID pbeId = PBE_ID_Key;
+       switch(context.algorithm()) {
+               case CSSM_ALGID_PKCS12_PBE_ENCR:
+                       pbeId = PBE_ID_Key;
+                       break;
+               case CSSM_ALGID_PKCS12_PBE_MAC:
+                       pbeId = PBE_ID_MAC;
+                       break;
+               default:
+                       /* really should not be here */
+                       assert(0);
+                       CssmError::throwMe(CSSMERR_CSP_INTERNAL_ERROR);
+       }
+       
+       /* Go */
+       CSSM_RETURN crtn = p12PbeGen(pwd,
+               salt, saltLen,
+               iterCount,
+               pbeId,
+               CSSM_ALGID_SHA1,                        // all we support for now
+               tmpCoder,
+               keyData->Data,
+               keyData->Length);
+       if(crtn) {
+               CssmError::throwMe(crtn);
+       }
+       
+       /* 
+        * Optional IV - makes no sense if we just did PBE_ID_MAC, but why
+        * bother restricting?
+        */
+       if(Param.Data) {
+               crtn = p12PbeGen(pwd,
+                       salt, saltLen,
+                       iterCount,
+                       PBE_ID_IV,
+                       CSSM_ALGID_SHA1,                        // all we support for now
+                       tmpCoder,
+                       Param.Data,
+                       Param.Length);
+               if(crtn) {
+                       CssmError::throwMe(crtn);
+               }
+       }
+}
+