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