Fixed: <rdar://problem/3674867> use new Security framework SPI's to reenable cert...
[WebKit-https.git] / WebKit / WebCoreSupport.subproj / WebNewKeyGeneration.c
1 /*
2  *  WebNewKeyGeneration.cpp
3  *  WebKit
4  *
5  *  Created by Chris Blumenberg on Mon Aug 23 2004.
6  *  Copyright (c) 2003 Apple Computer. All rights reserved.
7  *
8  */
9
10 #import <WebKit/WebNewKeyGeneration.h>
11
12 #ifdef USE_NEW_KEY_GENERATION
13
14 #import <WebKit/WebAssertions.h>
15
16 #import <Security/keyTemplates.h>
17 #import <Security/SecKeyPriv.h>                /* Security.framework SPI */
18
19 #import <security_cdsa_utils/cuEnc64.h>
20
21 /* hard coded params, some of which may come from the user in real life */
22 #define GNR_KEY_ALG                     CSSM_ALGID_RSA
23 #define GNR_SIG_ALG                     CSSM_ALGID_MD5WithRSA
24 #define GNR_SIG_ALGOID                  CSSMOID_MD5WithRSA
25
26 const SecAsn1Template NetscapeCertSequenceTemplate[] = {
27 { SEC_ASN1_SEQUENCE,
28     0, NULL, sizeof(NetscapeCertSequence) },
29 { SEC_ASN1_OBJECT_ID,
30     offsetof(NetscapeCertSequence, contentType), 0, 0 },
31 { SEC_ASN1_EXPLICIT | SEC_ASN1_CONSTRUCTED | 
32     SEC_ASN1_CONTEXT_SPECIFIC | 0 , 
33     offsetof(NetscapeCertSequence, certs),
34     kSecAsn1SequenceOfAnyTemplate, 0 },
35 { 0, 0, 0, 0 }
36 };
37
38 const SecAsn1Template PublicKeyAndChallengeTemplate[] = {
39     { SEC_ASN1_SEQUENCE,
40         0, NULL, sizeof(PublicKeyAndChallenge) },
41     { SEC_ASN1_INLINE,
42         offsetof(PublicKeyAndChallenge, spki),
43         kSecAsn1SubjectPublicKeyInfoTemplate, 0},
44     { SEC_ASN1_INLINE,
45         offsetof(PublicKeyAndChallenge, challenge),
46         kSecAsn1IA5StringTemplate, 0 },
47     { 0, 0, 0, 0}
48 };
49
50 const SecAsn1Template SignedPublicKeyAndChallengeTemplate[] = {
51     { SEC_ASN1_SEQUENCE,
52         0, NULL, sizeof(SignedPublicKeyAndChallenge) },
53     { SEC_ASN1_INLINE,
54         offsetof(SignedPublicKeyAndChallenge, pubKeyAndChallenge),
55         PublicKeyAndChallengeTemplate, 0 },
56     { SEC_ASN1_INLINE,
57         offsetof(SignedPublicKeyAndChallenge, algId),
58         kSecAsn1AlgorithmIDTemplate, 0 },
59     { SEC_ASN1_BIT_STRING,
60         offsetof(SignedPublicKeyAndChallenge, signature), 0, 0 },
61     { 0, 0, 0, 0 }
62 };
63
64 void gnrNullAlgParams(CSSM_X509_ALGORITHM_IDENTIFIER *algId);
65 CSSM_RETURN gnrSign(CSSM_CSP_HANDLE             cspHand,
66                     const CSSM_DATA             *plainText,
67                     SecKeyRef                   privKey,
68                     CSSM_ALGORITHMS             sigAlg,         // e.g., CSSM_ALGID_SHA1WithRSA
69                     CSSM_DATA                   *sig);
70 unsigned nssArraySize(const void **array);
71 bool addCertificateToKeychainFromData(const unsigned char *certData,
72                                       unsigned certDataLen,
73                                       unsigned certNum);
74
75 /*
76  * Given a context specified via a CSSM_CC_HANDLE, add a new
77  * CSSM_CONTEXT_ATTRIBUTE to the context as specified by AttributeType,
78  * AttributeLength, and an untyped pointer.
79  *
80  * This is currently used to add a second CSSM_KEY attribute when performing
81  * ops with algorithm CSSM_ALGID_FEED and CSSM_ALGID_FEECFILE.
82  */
83 static CSSM_RETURN gnrAddContextAttribute(CSSM_CC_HANDLE CCHandle,
84                                           uint32 AttributeType,
85                                           uint32 AttributeLength,
86                                           const void *AttributePtr)
87 {
88     CSSM_CONTEXT_ATTRIBUTE              newAttr;        
89     CSSM_RETURN                                 crtn;
90     
91     newAttr.AttributeType     = AttributeType;
92     newAttr.AttributeLength   = AttributeLength;
93     newAttr.Attribute.Data    = (CSSM_DATA_PTR)AttributePtr;
94     crtn = CSSM_UpdateContextAttributes(CCHandle, 1, &newAttr);
95     if(crtn) {
96         ERROR("CSSM_UpdateContextAttributes", crtn);
97     }
98     return crtn;
99 }
100
101 /*
102  * Given a public key as a SecKeyRef, obtain the key material in
103  * SubjectPublicKeyInfo format. This entails a NULL wrap to format
104  * in CSSM_KEYBLOB_RAW_FORMAT_X509 form. Caller must eventually
105  * free the returned key via CSSM_FreeKey().
106  */
107 static OSStatus gnrGetSubjPubKey(
108                                  CSSM_CSP_HANDLE        cspHand,
109                                  SecKeyRef secKey,
110                                  CSSM_KEY_PTR subjPubKey)               // RETURNED
111 {
112     CSSM_CC_HANDLE              ccHand;
113     CSSM_RETURN                 crtn;
114     CSSM_ACCESS_CREDENTIALS     creds;
115     const CSSM_KEY              *refPubKey;
116     OSStatus                    ortn;
117     
118     /* Get public key in CSSM form */
119     ortn = SecKeyGetCSSMKey(secKey, &refPubKey);
120     if(ortn) {
121         ERROR("SecKeyGetCSSMKey", ortn);
122         return ortn;
123     }
124     
125     /* NULL wrap via CSPDL */
126     memset(subjPubKey, 0, sizeof(CSSM_KEY));
127     memset(&creds, 0, sizeof(CSSM_ACCESS_CREDENTIALS));
128     crtn = CSSM_CSP_CreateSymmetricContext(cspHand,
129                                            CSSM_ALGID_NONE,
130                                            CSSM_ALGMODE_NONE,
131                                            &creds,                              // passPhrase
132                                            NULL,                                // wrapping key
133                                            NULL,                                // init vector
134                                            CSSM_PADDING_NONE,   // Padding
135                                            0,                                   // Params
136                                            &ccHand);
137     if(crtn) {
138         ERROR("gnrGetSubjPubKey CSSM_CSP_CreateSymmetricContext", 
139                      crtn);
140         return crtn;
141     }
142     
143     /*
144      * Specify X509 format' that is NOT the default for RSA (PKCS1 is)
145      */
146     crtn = gnrAddContextAttribute(ccHand,
147                                   CSSM_ATTRIBUTE_PUBLIC_KEY_FORMAT,
148                                   sizeof(uint32),
149                                   (void *)CSSM_KEYBLOB_RAW_FORMAT_X509);
150     if(crtn) {
151         ERROR("gnrAddContextAttribute", crtn);
152         goto errOut;
153     }
154     
155     crtn = CSSM_WrapKey(ccHand,
156                         &creds,
157                         refPubKey,
158                         NULL,                   // DescriptiveData
159                         subjPubKey);
160     if(crtn) {
161         ERROR("CSSM_WrapKey", crtn);
162     }
163 errOut:
164         CSSM_DeleteContext(ccHand);
165     return crtn;
166 }
167
168 /* 
169 * Set up a encoded NULL for CSSM_X509_ALGORITHM_IDENTIFIER.parameters.
170  */
171 void gnrNullAlgParams(CSSM_X509_ALGORITHM_IDENTIFIER *algId)
172 {
173     static const uint8 encNull[2] = { SEC_ASN1_NULL, 0 };
174     algId->parameters.Data = (uint8 *)encNull;
175     algId->parameters.Length = 2;
176 }
177
178 /*
179  * Sign specified plaintext. Caller must free signature data via
180  * gnrFreeCssmData().
181  */
182 CSSM_RETURN gnrSign(CSSM_CSP_HANDLE             cspHand,
183                     const CSSM_DATA             *plainText,
184                     SecKeyRef                   privKey,
185                     CSSM_ALGORITHMS             sigAlg,         // e.g., CSSM_ALGID_SHA1WithRSA
186                     CSSM_DATA                   *sig)           // allocated by CSP and RETURNED
187 {
188     CSSM_CC_HANDLE              ccHand;
189     CSSM_RETURN                 crtn;
190     const CSSM_KEY              *refPrivKey;
191     OSStatus                    ortn;
192     const CSSM_ACCESS_CREDENTIALS *creds;
193     
194     /* Get private key in CSSM form */
195     ortn = SecKeyGetCSSMKey(privKey, &refPrivKey);
196     if(ortn) {
197         ERROR("SecKeyGetCSSMKey", ortn);
198         return ortn;
199     }
200     
201     /* Get appropriate access credentials */
202     ortn = SecKeyGetCredentials(privKey,
203                                 CSSM_ACL_AUTHORIZATION_SIGN,
204                                 kSecCredentialTypeDefault,
205                                 &creds);
206     if(ortn) {
207         ERROR("SecKeyGetCredentials", ortn);
208         return ortn;
209     }
210     
211     /* cook up signature context */
212     crtn = CSSM_CSP_CreateSignatureContext(cspHand,
213                                            sigAlg,
214                                            creds,       
215                                            refPrivKey,
216                                            &ccHand);
217     if(crtn) {
218         ERROR("CSSM_CSP_CreateSignatureContext", ortn);
219         return crtn;
220     }
221     
222     /* go for it */
223     sig->Data = NULL;
224     sig->Length = 0;
225     crtn = CSSM_SignData(ccHand,
226                          plainText,
227                          1,
228                          CSSM_ALGID_NONE,
229                          sig);
230     if(crtn) {
231         ERROR("CSSM_SignData", ortn);
232     }
233     CSSM_DeleteContext(ccHand);
234     return crtn;
235 }
236
237 /*
238  * Free data mallocd on app's behalf by a CSSM module.
239  */
240 static void gnrFreeCssmData(
241                             CSSM_HANDLE         modHand,
242                             CSSM_DATA           *cdata)
243 {
244     CSSM_API_MEMORY_FUNCS memFuncs;
245     CSSM_RETURN crtn = CSSM_GetAPIMemoryFunctions(modHand, &memFuncs);
246     if(crtn) {
247         ERROR("CSSM_GetAPIMemoryFunctions", crtn);
248         /* oh well, leak and continue */
249     }
250     else {
251         memFuncs.free_func(cdata->Data, memFuncs.AllocRef);
252     }
253     return;
254 }
255
256 unsigned nssArraySize(const void **array)
257 {
258     unsigned count = 0;
259     if (array) {
260         while (*array++) {
261             count++;
262         }
263     }
264     return count;
265 }
266
267 CFStringRef signedPublicKeyAndChallengeString(unsigned keySize, CFStringRef challenge, CFStringRef keyDescription)
268 {
269     OSStatus            ortn;
270     CSSM_RETURN         crtn;
271     SecKeyRef           pubKey = NULL;
272     SecKeyRef           privKey = NULL;
273     CSSM_KEY            subjectPubKey;
274     bool                freeSubjPubKey = false;
275     CSSM_CSP_HANDLE     cspHand;
276     SecAsn1CoderRef     coder = NULL;
277     SignedPublicKeyAndChallenge spkc;
278     PublicKeyAndChallenge               *pkc = &spkc.pubKeyAndChallenge;
279     /* DER encoded spkc.pubKeyAndChallenge and spkc */
280     CSSM_DATA           encodedPkc = {0, NULL};         
281     CSSM_DATA           encodedSpkc = {0, NULL};
282     CSSM_DATA           signature = {0, NULL};
283     unsigned char       *spkcB64 = NULL;                // base64 encoded encodedSpkc
284     unsigned            spkcB64Len;
285     SecAccessRef        accessRef;
286     CFArrayRef          acls;
287     SecACLRef           acl;
288     CFStringRef         result = NULL;
289     
290     ortn = SecAccessCreate(keyDescription, NULL, &accessRef);
291     if (ortn) {
292         ERROR("***SecAccessCreate %d", ortn);
293         goto errOut;
294     }
295     ortn = SecAccessCopySelectedACLList(accessRef, CSSM_ACL_AUTHORIZATION_DECRYPT, &acls);
296     if (ortn) {
297         ERROR("***SecAccessCopySelectedACLList %d", ortn);
298         goto errOut;
299     }
300     acl = (SecACLRef)CFArrayGetValueAtIndex(acls, 0);
301     CFRelease(acls);
302     ortn = SecACLSetSimpleContents(acl, NULL, keyDescription, NULL);
303     if (ortn) {
304         ERROR("***SecACLSetSimpleContents %d", ortn);
305         goto errOut;
306     }
307     
308     // Cook up a key pair, just use any old params for now
309     ortn = SecKeyCreatePair(nil,                                        // in default KC
310                             GNR_KEY_ALG,                                // normally spec'd by user
311                             keySize,                                    // key size, ditto
312                             0,                                          // ContextHandle
313                             CSSM_KEYUSE_ANY,                            // might want to restrict this
314                             CSSM_KEYATTR_PERMANENT | CSSM_KEYATTR_EXTRACTABLE | 
315                             CSSM_KEYATTR_RETURN_REF,                    // pub attrs
316                             CSSM_KEYUSE_ANY,                            // might want to restrict this
317                             CSSM_KEYATTR_SENSITIVE | CSSM_KEYATTR_RETURN_REF |
318                             CSSM_KEYATTR_PERMANENT | CSSM_KEYATTR_EXTRACTABLE,
319                             accessRef,
320                             &pubKey,
321                             &privKey);
322     if (ortn != noErr) {
323         ERROR("***SecKeyCreatePair %d", ortn);
324         goto errOut;
325     }
326     
327     /* get handle of CSPDL for crypto ops */
328     ortn = SecKeyGetCSPHandle(privKey, &cspHand);
329     if (ortn != noErr) {
330         ERROR("***SecKeyGetCSPHandle", ortn);
331         goto errOut;
332     }
333     
334     /*
335      * Get the public key in encoded SubjectPublicKeyInfo form.
336      */
337     ortn = gnrGetSubjPubKey(cspHand, pubKey, &subjectPubKey);
338     if (ortn != noErr) {
339         goto errOut;
340     }
341     freeSubjPubKey = true;
342     
343     ortn = SecAsn1CoderCreate(&coder);
344     if (ortn != noErr) {
345         ERROR("***SecAsn1CoderCreate", ortn);
346         goto errOut;
347     }
348     
349     /*
350      * Cook up PublicKeyAndChallenge and DER-encode it.
351      * First, DER-decode the key's SubjectPublicKeyInfo.
352      */
353     memset(&spkc, 0, sizeof(spkc));
354     
355     ortn = SecAsn1DecodeData(coder, &subjectPubKey.KeyData, kSecAsn1SubjectPublicKeyInfoTemplate, &pkc->spki);
356     if (ortn != noErr) {
357         /* should never happen */
358         ERROR("***Error decoding subject public key info\n");
359         goto errOut;
360     }
361     
362     pkc->challenge.Length = CFStringGetLength(challenge);
363     if (pkc->challenge.Length == 0) {
364         pkc->challenge.Length = 1;
365         pkc->challenge.Data = (uint8 *)strdup("\0");
366     } else {
367         pkc->challenge.Data = (uint8 *)malloc(pkc->challenge.Length + 1);
368         CFStringGetCString(challenge,  (char *)pkc->challenge.Data, pkc->challenge.Length + 1, kCFStringEncodingASCII);
369     }
370     ortn = SecAsn1EncodeItem(coder, pkc, PublicKeyAndChallengeTemplate, &encodedPkc);
371     if (ortn != noErr) {
372         /* should never happen */
373         ERROR("***Error encoding PublicKeyAndChallenge\n");
374         goto errOut;
375     }
376     
377     /*
378      * Sign the encoded PublicKeyAndChallenge.
379      */
380     crtn = gnrSign(cspHand, &encodedPkc, privKey, GNR_SIG_ALG, &signature);
381     if (crtn) {
382         goto errOut;
383     }
384     
385     /*
386      * Cook up SignedPublicKeyAndChallenge, DER-encode that. 
387      * The PublicKeyAndChallenge stays in place where we originally
388      * created it before we signed it. Now we just add the algId
389      * and the signature proper.
390      */
391     spkc.algId.algorithm = GNR_SIG_ALGOID;
392     gnrNullAlgParams(&spkc.algId);
393     spkc.signature = signature;
394     /* convert to BIT length */
395     spkc.signature.Length *= 8;
396     ortn = SecAsn1EncodeItem(coder, &spkc, SignedPublicKeyAndChallengeTemplate, &encodedSpkc);
397     if (ortn != noErr) {
398         /* should never happen */
399         ERROR("***Error encoding SignedPublicKeyAndChallenge\n");
400         goto errOut;
401     }
402     
403     /*
404      * Finally base64 the result and write that to outFile.
405      * cuEnc64() gives us a NULL-terminated string; we strip off the NULL.
406      */
407     spkcB64 = cuEnc64(encodedSpkc.Data, encodedSpkc.Length, &spkcB64Len);
408     if (spkcB64 == NULL) {
409         /* should never happen */
410         FATAL("***Error base64-encoding the result\n");
411         goto errOut;
412     }
413     
414 errOut:
415     if (coder != NULL) {
416         SecAsn1CoderRelease(coder);
417     }
418     if (freeSubjPubKey) {
419         CSSM_FreeKey(cspHand, NULL, &subjectPubKey, CSSM_FALSE);
420     }
421     if (signature.Data) {
422         gnrFreeCssmData(cspHand, &signature);
423     }
424     if (pubKey) {
425         CFRelease(pubKey);
426     }
427     if (privKey) {
428         CFRelease(privKey);
429     }
430     if (accessRef) {
431         CFRelease(accessRef);
432     }    
433     if (pkc->challenge.Data) {
434         free(pkc->challenge.Data);
435     }
436     if (spkcB64) {
437         result = CFStringCreateWithCString(NULL, (const char *)spkcB64, kCFStringEncodingASCII);
438         free(spkcB64);
439     }
440     return result;
441 }
442
443 /* 
444 * Per-cert processing, called for each cert we extract from the 
445  * incoming blob.
446  */
447 bool addCertificateToKeychainFromData(const unsigned char *certData,
448                                       unsigned certDataLen,
449                                       unsigned certNum)
450 {
451     CSSM_DATA cert = {certDataLen, (uint8 *)certData};
452     SecCertificateRef certRef;
453     
454     /* Make a SecCertificateRef */
455     OSStatus ortn = SecCertificateCreateFromData(&cert, 
456                                                  CSSM_CERT_X_509v3,
457                                                  CSSM_CERT_ENCODING_DER,
458                                                  &certRef);
459     if (ortn != noErr) {
460         ERROR("SecCertificateCreateFromData returned %d", (int)ortn);
461         return false;
462     }
463     
464     /* 
465         * Add it to default keychain.
466         * Many people will be surprised that this op works without
467         * the user having to unlock the keychain. 
468         */
469     ortn = SecCertificateAddToKeychain(certRef, nil);
470     
471     /* Free the cert in any case */
472     CFRelease(certRef);
473     switch(ortn) {
474         case noErr:
475             break;
476         case errSecDuplicateItem:
477             /* Not uncommon, definitely not an error */
478             ERROR("cert %u already present in keychain", certNum);
479             break;
480         default:
481             ERROR("SecCertificateAddToKeychain returned %d", (int)ortn);
482             return false;
483     }
484
485     return true;
486 }
487
488 WebCertificateParseResult addCertificatesToKeychainFromData(const void *bytes, unsigned length)
489 {   
490     WebCertificateParseResult result = WebCertificateParseResultFailed;
491
492     /* DER-decode, first as NetscapeCertSequence */
493     SecAsn1CoderRef coder = NULL;
494     NetscapeCertSequence certSeq;
495     OSErr ortn;
496     
497     ortn = SecAsn1CoderCreate(&coder);
498     if (ortn == noErr) {
499         memset(&certSeq, 0, sizeof(certSeq));
500         ortn = SecAsn1Decode(coder, bytes, length, NetscapeCertSequenceTemplate, &certSeq);
501         if (ortn == noErr) {
502             if (certSeq.contentType.Length == CSSMOID_PKCS7_SignedData.Length &&
503                 memcmp(certSeq.contentType.Data, CSSMOID_PKCS7_SignedData.Data, certSeq.contentType.Length) == 0) {
504                 return WebCertificateParseResultPKCS7;
505             }
506             /*
507              * Last cert is a root, which we do NOT want to add
508              * to the user's keychain.
509              */
510             unsigned numCerts = nssArraySize((const void **)certSeq.certs) - 1;
511             unsigned i;
512             for (i=0; i<numCerts; i++) {
513                 CSSM_DATA *cert = certSeq.certs[i];
514                 result = addCertificateToKeychainFromData(cert->Data, cert->Length, i) ? WebCertificateParseResultSucceeded : WebCertificateParseResultFailed;
515             } 
516         } else {
517             /*
518              * Didn't appear to be a NetscapeCertSequence; assume it's just 
519              * a cert. FIXME: Netscape spec says the blob might also be PKCS7
520              * format, which we're not handling here.
521              */
522             result = addCertificateToKeychainFromData(bytes, length, 0) ? WebCertificateParseResultSucceeded : WebCertificateParseResultFailed;
523         }
524     }
525     
526     if (coder != NULL) {
527         SecAsn1CoderRelease(coder);
528     }
529
530     return result;
531 }
532
533 #endif /* USE_NEW_KEY_GENERATION */