146b117a1d093fa0a2e9d7dcc32c297190fe268e
[WebKit.git] / WebKit / WebCoreSupport.subproj / WebNewKeyGeneration.c
1 /*
2  * Copyright (C) 2005 Apple Computer, Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer. 
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution. 
13  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission. 
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #import <WebKit/WebNewKeyGeneration.h>
30
31 #ifdef USE_NEW_KEY_GENERATION
32
33 #import <Security/asn1Templates.h>
34 #import <Security/SecAsn1Coder.h>
35 #import <Security/secasn1t.h>
36 #import <Security/Security.h>
37
38 /*
39  * Netscape Certifiate Sequence is defined by Netscape as a PKCS7
40  * ContentInfo with a contentType of netscape-cert-sequence and a content
41  * consisting of a sequence of certificates.
42  *
43  * For simplicity - i.e., to avoid the general purpose ContentInfo
44  * polymorphism - we'll just hard-code this particular type right here.
45  *
46  * Inside the ContentInfo is an array of standard X509 certificates.
47  * We don't need to parse the certs themselves so they remain as
48  * opaque data blobs.
49  */
50 typedef struct {
51   CSSM_OID              contentType;            // netscape-cert-sequence
52   CSSM_DATA             **certs;
53 } NetscapeCertSequence;
54
55 extern const SecAsn1Template NetscapeCertSequenceTemplate[];
56
57 /*
58  * Public key/challenge, to send to CA.
59  *
60  * PublicKeyAndChallenge ::= SEQUENCE {
61  *
62  * ???\200?     spki SubjectPublicKeyInfo,
63  *      challenge IA5STRING
64  * }
65  *
66  * SignedPublicKeyAndChallenge ::= SEQUENCE {
67  *              publicKeyAndChallenge PublicKeyAndChallenge,
68  *              signatureAlgorithm AlgorithmIdentifier,
69  *              signature BIT STRING
70  * }
71  */
72 typedef struct {
73   CSSM_X509_SUBJECT_PUBLIC_KEY_INFO     spki;
74   CSSM_DATA                                                     challenge;      // ASCII
75 } PublicKeyAndChallenge;
76
77 typedef struct {
78   PublicKeyAndChallenge                         pubKeyAndChallenge;
79   CSSM_X509_ALGORITHM_IDENTIFIER                algId;
80   CSSM_DATA                                                     signature; // length in BITS
81 } SignedPublicKeyAndChallenge;
82
83 extern const SecAsn1Template PublicKeyAndChallengeTemplate[];
84 extern const SecAsn1Template SignedPublicKeyAndChallengeTemplate[];
85
86
87 #import <WebKit/WebAssertions.h>
88
89 #import <Security/keyTemplates.h>
90 #import <Security/SecKeyPriv.h>                /* Security.framework SPI */
91
92 #import <security_cdsa_utils/cuEnc64.h>
93
94 /* hard coded params, some of which may come from the user in real life */
95 #define GNR_KEY_ALG                     CSSM_ALGID_RSA
96 #define GNR_SIG_ALG                     CSSM_ALGID_MD5WithRSA
97 #define GNR_SIG_ALGOID                  CSSMOID_MD5WithRSA
98
99 const SecAsn1Template NetscapeCertSequenceTemplate[] = {
100 { SEC_ASN1_SEQUENCE,
101     0, NULL, sizeof(NetscapeCertSequence) },
102 { SEC_ASN1_OBJECT_ID,
103     offsetof(NetscapeCertSequence, contentType), 0, 0 },
104 { SEC_ASN1_EXPLICIT | SEC_ASN1_CONSTRUCTED | 
105     SEC_ASN1_CONTEXT_SPECIFIC | 0 , 
106     offsetof(NetscapeCertSequence, certs),
107     kSecAsn1SequenceOfAnyTemplate, 0 },
108 { 0, 0, 0, 0 }
109 };
110
111 const SecAsn1Template PublicKeyAndChallengeTemplate[] = {
112     { SEC_ASN1_SEQUENCE,
113         0, NULL, sizeof(PublicKeyAndChallenge) },
114     { SEC_ASN1_INLINE,
115         offsetof(PublicKeyAndChallenge, spki),
116         kSecAsn1SubjectPublicKeyInfoTemplate, 0},
117     { SEC_ASN1_INLINE,
118         offsetof(PublicKeyAndChallenge, challenge),
119         kSecAsn1IA5StringTemplate, 0 },
120     { 0, 0, 0, 0}
121 };
122
123 const SecAsn1Template SignedPublicKeyAndChallengeTemplate[] = {
124     { SEC_ASN1_SEQUENCE,
125         0, NULL, sizeof(SignedPublicKeyAndChallenge) },
126     { SEC_ASN1_INLINE,
127         offsetof(SignedPublicKeyAndChallenge, pubKeyAndChallenge),
128         PublicKeyAndChallengeTemplate, 0 },
129     { SEC_ASN1_INLINE,
130         offsetof(SignedPublicKeyAndChallenge, algId),
131         kSecAsn1AlgorithmIDTemplate, 0 },
132     { SEC_ASN1_BIT_STRING,
133         offsetof(SignedPublicKeyAndChallenge, signature), 0, 0 },
134     { 0, 0, 0, 0 }
135 };
136
137 void gnrNullAlgParams(CSSM_X509_ALGORITHM_IDENTIFIER *algId);
138 CSSM_RETURN gnrSign(CSSM_CSP_HANDLE             cspHand,
139                     const CSSM_DATA             *plainText,
140                     SecKeyRef                   privKey,
141                     CSSM_ALGORITHMS             sigAlg,         // e.g., CSSM_ALGID_SHA1WithRSA
142                     CSSM_DATA                   *sig);
143 unsigned nssArraySize(const void **array);
144 bool addCertificateToKeychainFromData(const unsigned char *certData,
145                                       unsigned certDataLen,
146                                       unsigned certNum);
147
148 /*
149  * Given a context specified via a CSSM_CC_HANDLE, add a new
150  * CSSM_CONTEXT_ATTRIBUTE to the context as specified by AttributeType,
151  * AttributeLength, and an untyped pointer.
152  *
153  * This is currently used to add a second CSSM_KEY attribute when performing
154  * ops with algorithm CSSM_ALGID_FEED and CSSM_ALGID_FEECFILE.
155  */
156 static CSSM_RETURN gnrAddContextAttribute(CSSM_CC_HANDLE CCHandle,
157                                           uint32 AttributeType,
158                                           uint32 AttributeLength,
159                                           const void *AttributePtr)
160 {
161     CSSM_CONTEXT_ATTRIBUTE              newAttr;        
162     CSSM_RETURN                                 crtn;
163     
164     newAttr.AttributeType     = AttributeType;
165     newAttr.AttributeLength   = AttributeLength;
166     newAttr.Attribute.Data    = (CSSM_DATA_PTR)AttributePtr;
167     crtn = CSSM_UpdateContextAttributes(CCHandle, 1, &newAttr);
168     if(crtn) {
169         ERROR("CSSM_UpdateContextAttributes", crtn);
170     }
171     return crtn;
172 }
173
174 /*
175  * Given a public key as a SecKeyRef, obtain the key material in
176  * SubjectPublicKeyInfo format. This entails a NULL wrap to format
177  * in CSSM_KEYBLOB_RAW_FORMAT_X509 form. Caller must eventually
178  * free the returned key via CSSM_FreeKey().
179  */
180 static OSStatus gnrGetSubjPubKey(
181                                  CSSM_CSP_HANDLE        cspHand,
182                                  SecKeyRef secKey,
183                                  CSSM_KEY_PTR subjPubKey)               // RETURNED
184 {
185     CSSM_CC_HANDLE              ccHand;
186     CSSM_RETURN                 crtn;
187     CSSM_ACCESS_CREDENTIALS     creds;
188     const CSSM_KEY              *refPubKey;
189     OSStatus                    ortn;
190     
191     /* Get public key in CSSM form */
192     ortn = SecKeyGetCSSMKey(secKey, &refPubKey);
193     if(ortn) {
194         ERROR("SecKeyGetCSSMKey", ortn);
195         return ortn;
196     }
197     
198     /* NULL wrap via CSPDL */
199     memset(subjPubKey, 0, sizeof(CSSM_KEY));
200     memset(&creds, 0, sizeof(CSSM_ACCESS_CREDENTIALS));
201     crtn = CSSM_CSP_CreateSymmetricContext(cspHand,
202                                            CSSM_ALGID_NONE,
203                                            CSSM_ALGMODE_NONE,
204                                            &creds,                              // passPhrase
205                                            NULL,                                // wrapping key
206                                            NULL,                                // init vector
207                                            CSSM_PADDING_NONE,   // Padding
208                                            0,                                   // Params
209                                            &ccHand);
210     if(crtn) {
211         ERROR("gnrGetSubjPubKey CSSM_CSP_CreateSymmetricContext", 
212                      crtn);
213         return crtn;
214     }
215     
216     /*
217      * Specify X509 format' that is NOT the default for RSA (PKCS1 is)
218      */
219     crtn = gnrAddContextAttribute(ccHand,
220                                   CSSM_ATTRIBUTE_PUBLIC_KEY_FORMAT,
221                                   sizeof(uint32),
222                                   (void *)CSSM_KEYBLOB_RAW_FORMAT_X509);
223     if(crtn) {
224         ERROR("gnrAddContextAttribute", crtn);
225         goto errOut;
226     }
227     
228     crtn = CSSM_WrapKey(ccHand,
229                         &creds,
230                         refPubKey,
231                         NULL,                   // DescriptiveData
232                         subjPubKey);
233     if(crtn) {
234         ERROR("CSSM_WrapKey", crtn);
235     }
236 errOut:
237         CSSM_DeleteContext(ccHand);
238     return crtn;
239 }
240
241 /* 
242 * Set up a encoded NULL for CSSM_X509_ALGORITHM_IDENTIFIER.parameters.
243  */
244 void gnrNullAlgParams(CSSM_X509_ALGORITHM_IDENTIFIER *algId)
245 {
246     static const uint8 encNull[2] = { SEC_ASN1_NULL, 0 };
247     algId->parameters.Data = (uint8 *)encNull;
248     algId->parameters.Length = 2;
249 }
250
251 /*
252  * Sign specified plaintext. Caller must free signature data via
253  * gnrFreeCssmData().
254  */
255 CSSM_RETURN gnrSign(CSSM_CSP_HANDLE             cspHand,
256                     const CSSM_DATA             *plainText,
257                     SecKeyRef                   privKey,
258                     CSSM_ALGORITHMS             sigAlg,         // e.g., CSSM_ALGID_SHA1WithRSA
259                     CSSM_DATA                   *sig)           // allocated by CSP and RETURNED
260 {
261     CSSM_CC_HANDLE              ccHand;
262     CSSM_RETURN                 crtn;
263     const CSSM_KEY              *refPrivKey;
264     OSStatus                    ortn;
265     const CSSM_ACCESS_CREDENTIALS *creds;
266     
267     /* Get private key in CSSM form */
268     ortn = SecKeyGetCSSMKey(privKey, &refPrivKey);
269     if(ortn) {
270         ERROR("SecKeyGetCSSMKey", ortn);
271         return ortn;
272     }
273     
274     /* Get appropriate access credentials */
275     ortn = SecKeyGetCredentials(privKey,
276                                 CSSM_ACL_AUTHORIZATION_SIGN,
277                                 kSecCredentialTypeDefault,
278                                 &creds);
279     if(ortn) {
280         ERROR("SecKeyGetCredentials", ortn);
281         return ortn;
282     }
283     
284     /* cook up signature context */
285     crtn = CSSM_CSP_CreateSignatureContext(cspHand,
286                                            sigAlg,
287                                            creds,       
288                                            refPrivKey,
289                                            &ccHand);
290     if(crtn) {
291         ERROR("CSSM_CSP_CreateSignatureContext", ortn);
292         return crtn;
293     }
294     
295     /* go for it */
296     sig->Data = NULL;
297     sig->Length = 0;
298     crtn = CSSM_SignData(ccHand,
299                          plainText,
300                          1,
301                          CSSM_ALGID_NONE,
302                          sig);
303     if(crtn) {
304         ERROR("CSSM_SignData", ortn);
305     }
306     CSSM_DeleteContext(ccHand);
307     return crtn;
308 }
309
310 /*
311  * Free data mallocd on app's behalf by a CSSM module.
312  */
313 static void gnrFreeCssmData(
314                             CSSM_HANDLE         modHand,
315                             CSSM_DATA           *cdata)
316 {
317     CSSM_API_MEMORY_FUNCS memFuncs;
318     CSSM_RETURN crtn = CSSM_GetAPIMemoryFunctions(modHand, &memFuncs);
319     if(crtn) {
320         ERROR("CSSM_GetAPIMemoryFunctions", crtn);
321         /* oh well, leak and continue */
322     }
323     else {
324         memFuncs.free_func(cdata->Data, memFuncs.AllocRef);
325     }
326     return;
327 }
328
329 unsigned nssArraySize(const void **array)
330 {
331     unsigned count = 0;
332     if (array) {
333         while (*array++) {
334             count++;
335         }
336     }
337     return count;
338 }
339
340 CFStringRef signedPublicKeyAndChallengeString(unsigned keySize, CFStringRef challenge, CFStringRef keyDescription)
341 {
342     OSStatus            ortn;
343     CSSM_RETURN         crtn;
344     SecKeyRef           pubKey = NULL;
345     SecKeyRef           privKey = NULL;
346     CSSM_KEY            subjectPubKey;
347     bool                freeSubjPubKey = false;
348     CSSM_CSP_HANDLE     cspHand;
349     SecAsn1CoderRef     coder = NULL;
350     SignedPublicKeyAndChallenge spkc;
351     PublicKeyAndChallenge               *pkc = &spkc.pubKeyAndChallenge;
352     /* DER encoded spkc.pubKeyAndChallenge and spkc */
353     CSSM_DATA           encodedPkc = {0, NULL};         
354     CSSM_DATA           encodedSpkc = {0, NULL};
355     CSSM_DATA           signature = {0, NULL};
356     unsigned char       *spkcB64 = NULL;                // base64 encoded encodedSpkc
357     unsigned            spkcB64Len;
358     SecAccessRef        accessRef;
359     CFArrayRef          acls;
360     SecACLRef           acl;
361     CFStringRef         result = NULL;
362     
363     ortn = SecAccessCreate(keyDescription, NULL, &accessRef);
364     if (ortn) {
365         ERROR("***SecAccessCreate %d", ortn);
366         goto errOut;
367     }
368     ortn = SecAccessCopySelectedACLList(accessRef, CSSM_ACL_AUTHORIZATION_DECRYPT, &acls);
369     if (ortn) {
370         ERROR("***SecAccessCopySelectedACLList %d", ortn);
371         goto errOut;
372     }
373     acl = (SecACLRef)CFArrayGetValueAtIndex(acls, 0);
374     CFRelease(acls);
375     ortn = SecACLSetSimpleContents(acl, NULL, keyDescription, NULL);
376     if (ortn) {
377         ERROR("***SecACLSetSimpleContents %d", ortn);
378         goto errOut;
379     }
380     
381     // Cook up a key pair, just use any old params for now
382     ortn = SecKeyCreatePair(nil,                                        // in default KC
383                             GNR_KEY_ALG,                                // normally spec'd by user
384                             keySize,                                    // key size, ditto
385                             0,                                          // ContextHandle
386                             CSSM_KEYUSE_ANY,                            // might want to restrict this
387                             CSSM_KEYATTR_PERMANENT | CSSM_KEYATTR_EXTRACTABLE | 
388                             CSSM_KEYATTR_RETURN_REF,                    // pub attrs
389                             CSSM_KEYUSE_ANY,                            // might want to restrict this
390                             CSSM_KEYATTR_SENSITIVE | CSSM_KEYATTR_RETURN_REF |
391                             CSSM_KEYATTR_PERMANENT | CSSM_KEYATTR_EXTRACTABLE,
392                             accessRef,
393                             &pubKey,
394                             &privKey);
395     if (ortn != noErr) {
396         ERROR("***SecKeyCreatePair %d", ortn);
397         goto errOut;
398     }
399     
400     /* get handle of CSPDL for crypto ops */
401     ortn = SecKeyGetCSPHandle(privKey, &cspHand);
402     if (ortn != noErr) {
403         ERROR("***SecKeyGetCSPHandle", ortn);
404         goto errOut;
405     }
406     
407     /*
408      * Get the public key in encoded SubjectPublicKeyInfo form.
409      */
410     ortn = gnrGetSubjPubKey(cspHand, pubKey, &subjectPubKey);
411     if (ortn != noErr) {
412         goto errOut;
413     }
414     freeSubjPubKey = true;
415     
416     ortn = SecAsn1CoderCreate(&coder);
417     if (ortn != noErr) {
418         ERROR("***SecAsn1CoderCreate", ortn);
419         goto errOut;
420     }
421     
422     /*
423      * Cook up PublicKeyAndChallenge and DER-encode it.
424      * First, DER-decode the key's SubjectPublicKeyInfo.
425      */
426     memset(&spkc, 0, sizeof(spkc));
427     
428     ortn = SecAsn1DecodeData(coder, &subjectPubKey.KeyData, kSecAsn1SubjectPublicKeyInfoTemplate, &pkc->spki);
429     if (ortn != noErr) {
430         /* should never happen */
431         ERROR("***Error decoding subject public key info\n");
432         goto errOut;
433     }
434     
435     pkc->challenge.Length = CFStringGetLength(challenge);
436     if (pkc->challenge.Length == 0) {
437         pkc->challenge.Length = 1;
438         pkc->challenge.Data = (uint8 *)strdup("\0");
439     } else {
440         pkc->challenge.Data = (uint8 *)malloc(pkc->challenge.Length + 1);
441         CFStringGetCString(challenge,  (char *)pkc->challenge.Data, pkc->challenge.Length + 1, kCFStringEncodingASCII);
442     }
443     ortn = SecAsn1EncodeItem(coder, pkc, PublicKeyAndChallengeTemplate, &encodedPkc);
444     if (ortn != noErr) {
445         /* should never happen */
446         ERROR("***Error encoding PublicKeyAndChallenge\n");
447         goto errOut;
448     }
449     
450     /*
451      * Sign the encoded PublicKeyAndChallenge.
452      */
453     crtn = gnrSign(cspHand, &encodedPkc, privKey, GNR_SIG_ALG, &signature);
454     if (crtn) {
455         goto errOut;
456     }
457     
458     /*
459      * Cook up SignedPublicKeyAndChallenge, DER-encode that. 
460      * The PublicKeyAndChallenge stays in place where we originally
461      * created it before we signed it. Now we just add the algId
462      * and the signature proper.
463      */
464     spkc.algId.algorithm = GNR_SIG_ALGOID;
465     gnrNullAlgParams(&spkc.algId);
466     spkc.signature = signature;
467     /* convert to BIT length */
468     spkc.signature.Length *= 8;
469     ortn = SecAsn1EncodeItem(coder, &spkc, SignedPublicKeyAndChallengeTemplate, &encodedSpkc);
470     if (ortn != noErr) {
471         /* should never happen */
472         ERROR("***Error encoding SignedPublicKeyAndChallenge\n");
473         goto errOut;
474     }
475     
476     /*
477      * Finally base64 the result and write that to outFile.
478      * cuEnc64() gives us a NULL-terminated string; we strip off the NULL.
479      */
480     spkcB64 = cuEnc64(encodedSpkc.Data, encodedSpkc.Length, &spkcB64Len);
481     if (spkcB64 == NULL) {
482         /* should never happen */
483         FATAL("***Error base64-encoding the result\n");
484         goto errOut;
485     }
486     
487 errOut:
488     if (coder != NULL) {
489         SecAsn1CoderRelease(coder);
490     }
491     if (freeSubjPubKey) {
492         CSSM_FreeKey(cspHand, NULL, &subjectPubKey, CSSM_FALSE);
493     }
494     if (signature.Data) {
495         gnrFreeCssmData(cspHand, &signature);
496     }
497     if (pubKey) {
498         CFRelease(pubKey);
499     }
500     if (privKey) {
501         CFRelease(privKey);
502     }
503     if (accessRef) {
504         CFRelease(accessRef);
505     }    
506     if (pkc->challenge.Data) {
507         free(pkc->challenge.Data);
508     }
509     if (spkcB64) {
510         result = CFStringCreateWithCString(NULL, (const char *)spkcB64, kCFStringEncodingASCII);
511         free(spkcB64);
512     }
513     return result;
514 }
515
516 /* 
517 * Per-cert processing, called for each cert we extract from the 
518  * incoming blob.
519  */
520 bool addCertificateToKeychainFromData(const unsigned char *certData,
521                                       unsigned certDataLen,
522                                       unsigned certNum)
523 {
524     CSSM_DATA cert = {certDataLen, (uint8 *)certData};
525     SecCertificateRef certRef;
526     
527     /* Make a SecCertificateRef */
528     OSStatus ortn = SecCertificateCreateFromData(&cert, 
529                                                  CSSM_CERT_X_509v3,
530                                                  CSSM_CERT_ENCODING_DER,
531                                                  &certRef);
532     if (ortn != noErr) {
533         ERROR("SecCertificateCreateFromData returned %d", (int)ortn);
534         return false;
535     }
536     
537     /* 
538         * Add it to default keychain.
539         * Many people will be surprised that this op works without
540         * the user having to unlock the keychain. 
541         */
542     ortn = SecCertificateAddToKeychain(certRef, nil);
543     
544     /* Free the cert in any case */
545     CFRelease(certRef);
546     switch(ortn) {
547         case noErr:
548             break;
549         case errSecDuplicateItem:
550             /* Not uncommon, definitely not an error */
551             ERROR("cert %u already present in keychain", certNum);
552             break;
553         default:
554             ERROR("SecCertificateAddToKeychain returned %d", (int)ortn);
555             return false;
556     }
557
558     return true;
559 }
560
561 WebCertificateParseResult addCertificatesToKeychainFromData(const void *bytes, unsigned length)
562 {   
563     WebCertificateParseResult result = WebCertificateParseResultFailed;
564
565     /* DER-decode, first as NetscapeCertSequence */
566     SecAsn1CoderRef coder = NULL;
567     NetscapeCertSequence certSeq;
568     OSErr ortn;
569     
570     ortn = SecAsn1CoderCreate(&coder);
571     if (ortn == noErr) {
572         memset(&certSeq, 0, sizeof(certSeq));
573         ortn = SecAsn1Decode(coder, bytes, length, NetscapeCertSequenceTemplate, &certSeq);
574         if (ortn == noErr) {
575             if (certSeq.contentType.Length == CSSMOID_PKCS7_SignedData.Length &&
576                 memcmp(certSeq.contentType.Data, CSSMOID_PKCS7_SignedData.Data, certSeq.contentType.Length) == 0) {
577                 return WebCertificateParseResultPKCS7;
578             }
579             /*
580              * Last cert is a root, which we do NOT want to add
581              * to the user's keychain.
582              */
583             unsigned numCerts = nssArraySize((const void **)certSeq.certs) - 1;
584             unsigned i;
585             for (i=0; i<numCerts; i++) {
586                 CSSM_DATA *cert = certSeq.certs[i];
587                 result = addCertificateToKeychainFromData(cert->Data, cert->Length, i) ? WebCertificateParseResultSucceeded : WebCertificateParseResultFailed;
588             } 
589         } else {
590             /*
591              * Didn't appear to be a NetscapeCertSequence; assume it's just 
592              * a cert. FIXME: Netscape spec says the blob might also be PKCS7
593              * format, which we're not handling here.
594              */
595             result = addCertificateToKeychainFromData(bytes, length, 0) ? WebCertificateParseResultSucceeded : WebCertificateParseResultFailed;
596         }
597     }
598     
599     if (coder != NULL) {
600         SecAsn1CoderRelease(coder);
601     }
602
603     return result;
604 }
605
606 #endif /* USE_NEW_KEY_GENERATION */