2 * WebNewKeyGeneration.c
5 * Created by Chris Blumenberg on Mon Aug 23 2004.
6 * Copyright (c) 2003 Apple Computer. All rights reserved.
10 #import <WebKit/WebNewKeyGeneration.h>
12 #ifdef USE_NEW_KEY_GENERATION
14 #import <Security/asn1Templates.h>
15 #import <Security/SecAsn1Coder.h>
16 #import <Security/secasn1t.h>
17 #import <Security/Security.h>
20 * Netscape Certifiate Sequence is defined by Netscape as a PKCS7
21 * ContentInfo with a contentType of netscape-cert-sequence and a content
22 * consisting of a sequence of certificates.
24 * For simplicity - i.e., to avoid the general purpose ContentInfo
25 * polymorphism - we'll just hard-code this particular type right here.
27 * Inside the ContentInfo is an array of standard X509 certificates.
28 * We don't need to parse the certs themselves so they remain as
32 CSSM_OID contentType; // netscape-cert-sequence
34 } NetscapeCertSequence;
36 extern const SecAsn1Template NetscapeCertSequenceTemplate[];
39 * Public key/challenge, to send to CA.
41 * PublicKeyAndChallenge ::= SEQUENCE {
43 * ???\200? spki SubjectPublicKeyInfo,
47 * SignedPublicKeyAndChallenge ::= SEQUENCE {
48 * publicKeyAndChallenge PublicKeyAndChallenge,
49 * signatureAlgorithm AlgorithmIdentifier,
50 * signature BIT STRING
54 CSSM_X509_SUBJECT_PUBLIC_KEY_INFO spki;
55 CSSM_DATA challenge; // ASCII
56 } PublicKeyAndChallenge;
59 PublicKeyAndChallenge pubKeyAndChallenge;
60 CSSM_X509_ALGORITHM_IDENTIFIER algId;
61 CSSM_DATA signature; // length in BITS
62 } SignedPublicKeyAndChallenge;
64 extern const SecAsn1Template PublicKeyAndChallengeTemplate[];
65 extern const SecAsn1Template SignedPublicKeyAndChallengeTemplate[];
68 #import <WebKit/WebAssertions.h>
70 #import <Security/keyTemplates.h>
71 #import <Security/SecKeyPriv.h> /* Security.framework SPI */
73 #import <security_cdsa_utils/cuEnc64.h>
75 /* hard coded params, some of which may come from the user in real life */
76 #define GNR_KEY_ALG CSSM_ALGID_RSA
77 #define GNR_SIG_ALG CSSM_ALGID_MD5WithRSA
78 #define GNR_SIG_ALGOID CSSMOID_MD5WithRSA
80 const SecAsn1Template NetscapeCertSequenceTemplate[] = {
82 0, NULL, sizeof(NetscapeCertSequence) },
84 offsetof(NetscapeCertSequence, contentType), 0, 0 },
85 { SEC_ASN1_EXPLICIT | SEC_ASN1_CONSTRUCTED |
86 SEC_ASN1_CONTEXT_SPECIFIC | 0 ,
87 offsetof(NetscapeCertSequence, certs),
88 kSecAsn1SequenceOfAnyTemplate, 0 },
92 const SecAsn1Template PublicKeyAndChallengeTemplate[] = {
94 0, NULL, sizeof(PublicKeyAndChallenge) },
96 offsetof(PublicKeyAndChallenge, spki),
97 kSecAsn1SubjectPublicKeyInfoTemplate, 0},
99 offsetof(PublicKeyAndChallenge, challenge),
100 kSecAsn1IA5StringTemplate, 0 },
104 const SecAsn1Template SignedPublicKeyAndChallengeTemplate[] = {
106 0, NULL, sizeof(SignedPublicKeyAndChallenge) },
108 offsetof(SignedPublicKeyAndChallenge, pubKeyAndChallenge),
109 PublicKeyAndChallengeTemplate, 0 },
111 offsetof(SignedPublicKeyAndChallenge, algId),
112 kSecAsn1AlgorithmIDTemplate, 0 },
113 { SEC_ASN1_BIT_STRING,
114 offsetof(SignedPublicKeyAndChallenge, signature), 0, 0 },
118 void gnrNullAlgParams(CSSM_X509_ALGORITHM_IDENTIFIER *algId);
119 CSSM_RETURN gnrSign(CSSM_CSP_HANDLE cspHand,
120 const CSSM_DATA *plainText,
122 CSSM_ALGORITHMS sigAlg, // e.g., CSSM_ALGID_SHA1WithRSA
124 unsigned nssArraySize(const void **array);
125 bool addCertificateToKeychainFromData(const unsigned char *certData,
126 unsigned certDataLen,
130 * Given a context specified via a CSSM_CC_HANDLE, add a new
131 * CSSM_CONTEXT_ATTRIBUTE to the context as specified by AttributeType,
132 * AttributeLength, and an untyped pointer.
134 * This is currently used to add a second CSSM_KEY attribute when performing
135 * ops with algorithm CSSM_ALGID_FEED and CSSM_ALGID_FEECFILE.
137 static CSSM_RETURN gnrAddContextAttribute(CSSM_CC_HANDLE CCHandle,
138 uint32 AttributeType,
139 uint32 AttributeLength,
140 const void *AttributePtr)
142 CSSM_CONTEXT_ATTRIBUTE newAttr;
145 newAttr.AttributeType = AttributeType;
146 newAttr.AttributeLength = AttributeLength;
147 newAttr.Attribute.Data = (CSSM_DATA_PTR)AttributePtr;
148 crtn = CSSM_UpdateContextAttributes(CCHandle, 1, &newAttr);
150 ERROR("CSSM_UpdateContextAttributes", crtn);
156 * Given a public key as a SecKeyRef, obtain the key material in
157 * SubjectPublicKeyInfo format. This entails a NULL wrap to format
158 * in CSSM_KEYBLOB_RAW_FORMAT_X509 form. Caller must eventually
159 * free the returned key via CSSM_FreeKey().
161 static OSStatus gnrGetSubjPubKey(
162 CSSM_CSP_HANDLE cspHand,
164 CSSM_KEY_PTR subjPubKey) // RETURNED
166 CSSM_CC_HANDLE ccHand;
168 CSSM_ACCESS_CREDENTIALS creds;
169 const CSSM_KEY *refPubKey;
172 /* Get public key in CSSM form */
173 ortn = SecKeyGetCSSMKey(secKey, &refPubKey);
175 ERROR("SecKeyGetCSSMKey", ortn);
179 /* NULL wrap via CSPDL */
180 memset(subjPubKey, 0, sizeof(CSSM_KEY));
181 memset(&creds, 0, sizeof(CSSM_ACCESS_CREDENTIALS));
182 crtn = CSSM_CSP_CreateSymmetricContext(cspHand,
185 &creds, // passPhrase
186 NULL, // wrapping key
188 CSSM_PADDING_NONE, // Padding
192 ERROR("gnrGetSubjPubKey CSSM_CSP_CreateSymmetricContext",
198 * Specify X509 format' that is NOT the default for RSA (PKCS1 is)
200 crtn = gnrAddContextAttribute(ccHand,
201 CSSM_ATTRIBUTE_PUBLIC_KEY_FORMAT,
203 (void *)CSSM_KEYBLOB_RAW_FORMAT_X509);
205 ERROR("gnrAddContextAttribute", crtn);
209 crtn = CSSM_WrapKey(ccHand,
212 NULL, // DescriptiveData
215 ERROR("CSSM_WrapKey", crtn);
218 CSSM_DeleteContext(ccHand);
223 * Set up a encoded NULL for CSSM_X509_ALGORITHM_IDENTIFIER.parameters.
225 void gnrNullAlgParams(CSSM_X509_ALGORITHM_IDENTIFIER *algId)
227 static const uint8 encNull[2] = { SEC_ASN1_NULL, 0 };
228 algId->parameters.Data = (uint8 *)encNull;
229 algId->parameters.Length = 2;
233 * Sign specified plaintext. Caller must free signature data via
236 CSSM_RETURN gnrSign(CSSM_CSP_HANDLE cspHand,
237 const CSSM_DATA *plainText,
239 CSSM_ALGORITHMS sigAlg, // e.g., CSSM_ALGID_SHA1WithRSA
240 CSSM_DATA *sig) // allocated by CSP and RETURNED
242 CSSM_CC_HANDLE ccHand;
244 const CSSM_KEY *refPrivKey;
246 const CSSM_ACCESS_CREDENTIALS *creds;
248 /* Get private key in CSSM form */
249 ortn = SecKeyGetCSSMKey(privKey, &refPrivKey);
251 ERROR("SecKeyGetCSSMKey", ortn);
255 /* Get appropriate access credentials */
256 ortn = SecKeyGetCredentials(privKey,
257 CSSM_ACL_AUTHORIZATION_SIGN,
258 kSecCredentialTypeDefault,
261 ERROR("SecKeyGetCredentials", ortn);
265 /* cook up signature context */
266 crtn = CSSM_CSP_CreateSignatureContext(cspHand,
272 ERROR("CSSM_CSP_CreateSignatureContext", ortn);
279 crtn = CSSM_SignData(ccHand,
285 ERROR("CSSM_SignData", ortn);
287 CSSM_DeleteContext(ccHand);
292 * Free data mallocd on app's behalf by a CSSM module.
294 static void gnrFreeCssmData(
298 CSSM_API_MEMORY_FUNCS memFuncs;
299 CSSM_RETURN crtn = CSSM_GetAPIMemoryFunctions(modHand, &memFuncs);
301 ERROR("CSSM_GetAPIMemoryFunctions", crtn);
302 /* oh well, leak and continue */
305 memFuncs.free_func(cdata->Data, memFuncs.AllocRef);
310 unsigned nssArraySize(const void **array)
321 CFStringRef signedPublicKeyAndChallengeString(unsigned keySize, CFStringRef challenge, CFStringRef keyDescription)
325 SecKeyRef pubKey = NULL;
326 SecKeyRef privKey = NULL;
327 CSSM_KEY subjectPubKey;
328 bool freeSubjPubKey = false;
329 CSSM_CSP_HANDLE cspHand;
330 SecAsn1CoderRef coder = NULL;
331 SignedPublicKeyAndChallenge spkc;
332 PublicKeyAndChallenge *pkc = &spkc.pubKeyAndChallenge;
333 /* DER encoded spkc.pubKeyAndChallenge and spkc */
334 CSSM_DATA encodedPkc = {0, NULL};
335 CSSM_DATA encodedSpkc = {0, NULL};
336 CSSM_DATA signature = {0, NULL};
337 unsigned char *spkcB64 = NULL; // base64 encoded encodedSpkc
339 SecAccessRef accessRef;
342 CFStringRef result = NULL;
344 ortn = SecAccessCreate(keyDescription, NULL, &accessRef);
346 ERROR("***SecAccessCreate %d", ortn);
349 ortn = SecAccessCopySelectedACLList(accessRef, CSSM_ACL_AUTHORIZATION_DECRYPT, &acls);
351 ERROR("***SecAccessCopySelectedACLList %d", ortn);
354 acl = (SecACLRef)CFArrayGetValueAtIndex(acls, 0);
356 ortn = SecACLSetSimpleContents(acl, NULL, keyDescription, NULL);
358 ERROR("***SecACLSetSimpleContents %d", ortn);
362 // Cook up a key pair, just use any old params for now
363 ortn = SecKeyCreatePair(nil, // in default KC
364 GNR_KEY_ALG, // normally spec'd by user
365 keySize, // key size, ditto
367 CSSM_KEYUSE_ANY, // might want to restrict this
368 CSSM_KEYATTR_PERMANENT | CSSM_KEYATTR_EXTRACTABLE |
369 CSSM_KEYATTR_RETURN_REF, // pub attrs
370 CSSM_KEYUSE_ANY, // might want to restrict this
371 CSSM_KEYATTR_SENSITIVE | CSSM_KEYATTR_RETURN_REF |
372 CSSM_KEYATTR_PERMANENT | CSSM_KEYATTR_EXTRACTABLE,
377 ERROR("***SecKeyCreatePair %d", ortn);
381 /* get handle of CSPDL for crypto ops */
382 ortn = SecKeyGetCSPHandle(privKey, &cspHand);
384 ERROR("***SecKeyGetCSPHandle", ortn);
389 * Get the public key in encoded SubjectPublicKeyInfo form.
391 ortn = gnrGetSubjPubKey(cspHand, pubKey, &subjectPubKey);
395 freeSubjPubKey = true;
397 ortn = SecAsn1CoderCreate(&coder);
399 ERROR("***SecAsn1CoderCreate", ortn);
404 * Cook up PublicKeyAndChallenge and DER-encode it.
405 * First, DER-decode the key's SubjectPublicKeyInfo.
407 memset(&spkc, 0, sizeof(spkc));
409 ortn = SecAsn1DecodeData(coder, &subjectPubKey.KeyData, kSecAsn1SubjectPublicKeyInfoTemplate, &pkc->spki);
411 /* should never happen */
412 ERROR("***Error decoding subject public key info\n");
416 pkc->challenge.Length = CFStringGetLength(challenge);
417 if (pkc->challenge.Length == 0) {
418 pkc->challenge.Length = 1;
419 pkc->challenge.Data = (uint8 *)strdup("\0");
421 pkc->challenge.Data = (uint8 *)malloc(pkc->challenge.Length + 1);
422 CFStringGetCString(challenge, (char *)pkc->challenge.Data, pkc->challenge.Length + 1, kCFStringEncodingASCII);
424 ortn = SecAsn1EncodeItem(coder, pkc, PublicKeyAndChallengeTemplate, &encodedPkc);
426 /* should never happen */
427 ERROR("***Error encoding PublicKeyAndChallenge\n");
432 * Sign the encoded PublicKeyAndChallenge.
434 crtn = gnrSign(cspHand, &encodedPkc, privKey, GNR_SIG_ALG, &signature);
440 * Cook up SignedPublicKeyAndChallenge, DER-encode that.
441 * The PublicKeyAndChallenge stays in place where we originally
442 * created it before we signed it. Now we just add the algId
443 * and the signature proper.
445 spkc.algId.algorithm = GNR_SIG_ALGOID;
446 gnrNullAlgParams(&spkc.algId);
447 spkc.signature = signature;
448 /* convert to BIT length */
449 spkc.signature.Length *= 8;
450 ortn = SecAsn1EncodeItem(coder, &spkc, SignedPublicKeyAndChallengeTemplate, &encodedSpkc);
452 /* should never happen */
453 ERROR("***Error encoding SignedPublicKeyAndChallenge\n");
458 * Finally base64 the result and write that to outFile.
459 * cuEnc64() gives us a NULL-terminated string; we strip off the NULL.
461 spkcB64 = cuEnc64(encodedSpkc.Data, encodedSpkc.Length, &spkcB64Len);
462 if (spkcB64 == NULL) {
463 /* should never happen */
464 FATAL("***Error base64-encoding the result\n");
470 SecAsn1CoderRelease(coder);
472 if (freeSubjPubKey) {
473 CSSM_FreeKey(cspHand, NULL, &subjectPubKey, CSSM_FALSE);
475 if (signature.Data) {
476 gnrFreeCssmData(cspHand, &signature);
485 CFRelease(accessRef);
487 if (pkc->challenge.Data) {
488 free(pkc->challenge.Data);
491 result = CFStringCreateWithCString(NULL, (const char *)spkcB64, kCFStringEncodingASCII);
498 * Per-cert processing, called for each cert we extract from the
501 bool addCertificateToKeychainFromData(const unsigned char *certData,
502 unsigned certDataLen,
505 CSSM_DATA cert = {certDataLen, (uint8 *)certData};
506 SecCertificateRef certRef;
508 /* Make a SecCertificateRef */
509 OSStatus ortn = SecCertificateCreateFromData(&cert,
511 CSSM_CERT_ENCODING_DER,
514 ERROR("SecCertificateCreateFromData returned %d", (int)ortn);
519 * Add it to default keychain.
520 * Many people will be surprised that this op works without
521 * the user having to unlock the keychain.
523 ortn = SecCertificateAddToKeychain(certRef, nil);
525 /* Free the cert in any case */
530 case errSecDuplicateItem:
531 /* Not uncommon, definitely not an error */
532 ERROR("cert %u already present in keychain", certNum);
535 ERROR("SecCertificateAddToKeychain returned %d", (int)ortn);
542 WebCertificateParseResult addCertificatesToKeychainFromData(const void *bytes, unsigned length)
544 WebCertificateParseResult result = WebCertificateParseResultFailed;
546 /* DER-decode, first as NetscapeCertSequence */
547 SecAsn1CoderRef coder = NULL;
548 NetscapeCertSequence certSeq;
551 ortn = SecAsn1CoderCreate(&coder);
553 memset(&certSeq, 0, sizeof(certSeq));
554 ortn = SecAsn1Decode(coder, bytes, length, NetscapeCertSequenceTemplate, &certSeq);
556 if (certSeq.contentType.Length == CSSMOID_PKCS7_SignedData.Length &&
557 memcmp(certSeq.contentType.Data, CSSMOID_PKCS7_SignedData.Data, certSeq.contentType.Length) == 0) {
558 return WebCertificateParseResultPKCS7;
561 * Last cert is a root, which we do NOT want to add
562 * to the user's keychain.
564 unsigned numCerts = nssArraySize((const void **)certSeq.certs) - 1;
566 for (i=0; i<numCerts; i++) {
567 CSSM_DATA *cert = certSeq.certs[i];
568 result = addCertificateToKeychainFromData(cert->Data, cert->Length, i) ? WebCertificateParseResultSucceeded : WebCertificateParseResultFailed;
572 * Didn't appear to be a NetscapeCertSequence; assume it's just
573 * a cert. FIXME: Netscape spec says the blob might also be PKCS7
574 * format, which we're not handling here.
576 result = addCertificateToKeychainFromData(bytes, length, 0) ? WebCertificateParseResultSucceeded : WebCertificateParseResultFailed;
581 SecAsn1CoderRelease(coder);
587 #endif /* USE_NEW_KEY_GENERATION */