| /* This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| |
| #include "seccomon.h" |
| #include "nss.h" |
| #include "keyhi.h" |
| #include "cert.h" |
| #include "pk11func.h" |
| #include "secmod.h" |
| #include "cmmf.h" |
| #include "crmf.h" |
| #include "base64.h" |
| #include "secasn1.h" |
| #include "cryptohi.h" |
| #include <string.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| |
| #define DEFAULT_ALLOC_SIZE 200 |
| #define DEFAULT_CGI_VARS 20 |
| |
| typedef struct CGIVariableStr { |
| char *name; |
| char *value; |
| } CGIVariable; |
| |
| typedef struct CGIVarTableStr { |
| CGIVariable **variables; |
| int numVars; |
| int numAlloc; |
| } CGIVarTable; |
| |
| typedef struct CertResponseInfoStr { |
| CERTCertificate *cert; |
| long certReqID; |
| } CertResponseInfo; |
| |
| typedef struct ChallengeCreationInfoStr { |
| long random; |
| SECKEYPublicKey *pubKey; |
| } ChallengeCreationInfo; |
| |
| char *missingVar = NULL; |
| |
| /* |
| * Error values. |
| */ |
| typedef enum { |
| NO_ERROR = 0, |
| NSS_INIT_FAILED, |
| AUTH_FAILED, |
| REQ_CGI_VAR_NOT_PRESENT, |
| CRMF_REQ_NOT_PRESENT, |
| BAD_ASCII_FOR_REQ, |
| CGI_VAR_MISSING, |
| COULD_NOT_FIND_CA, |
| COULD_NOT_DECODE_REQS, |
| OUT_OF_MEMORY, |
| ERROR_RETRIEVING_REQUEST_MSG, |
| ERROR_RETRIEVING_CERT_REQUEST, |
| ERROR_RETRIEVING_SUBJECT_FROM_REQ, |
| ERROR_RETRIEVING_PUBLIC_KEY_FROM_REQ, |
| ERROR_CREATING_NEW_CERTIFICATE, |
| COULD_NOT_START_EXTENSIONS, |
| ERROR_RETRIEVING_EXT_FROM_REQ, |
| ERROR_ADDING_EXT_TO_CERT, |
| ERROR_ENDING_EXTENSIONS, |
| COULD_NOT_FIND_ISSUER_PRIVATE_KEY, |
| UNSUPPORTED_SIGN_OPERATION_FOR_ISSUER, |
| ERROR_SETTING_SIGN_ALG, |
| ERROR_ENCODING_NEW_CERT, |
| ERROR_SIGNING_NEW_CERT, |
| ERROR_CREATING_CERT_REP_CONTENT, |
| ERROR_CREATING_SINGLE_CERT_RESPONSE, |
| ERROR_SETTING_CERT_RESPONSES, |
| ERROR_CREATING_CA_LIST, |
| ERROR_ADDING_ISSUER_TO_CA_LIST, |
| ERROR_ENCODING_CERT_REP_CONTENT, |
| NO_POP_FOR_REQUEST, |
| UNSUPPORTED_POP, |
| ERROR_RETRIEVING_POP_SIGN_KEY, |
| ERROR_RETRIEVING_ALG_ID_FROM_SIGN_KEY, |
| ERROR_RETRIEVING_SIGNATURE_FROM_POP_SIGN_KEY, |
| DO_CHALLENGE_RESPONSE, |
| ERROR_RETRIEVING_PUB_KEY_FROM_NEW_CERT, |
| ERROR_ENCODING_CERT_REQ_FOR_POP, |
| ERROR_VERIFYING_SIGNATURE_POP, |
| ERROR_RETRIEVING_PUB_KEY_FOR_CHALL, |
| ERROR_CREATING_EMPTY_CHAL_CONTENT, |
| ERROR_EXTRACTING_GEN_NAME_FROM_ISSUER, |
| ERROR_SETTING_CHALLENGE, |
| ERROR_ENCODING_CHALL, |
| ERROR_CONVERTING_CHALL_TO_BASE64, |
| ERROR_CONVERTING_RESP_FROM_CHALL_TO_BIN, |
| ERROR_CREATING_KEY_RESP_FROM_DER, |
| ERROR_RETRIEVING_CLIENT_RESPONSE_TO_CHALLENGE, |
| ERROR_RETURNED_CHALL_NOT_VALUE_EXPECTED, |
| ERROR_GETTING_KEY_ENCIPHERMENT, |
| ERROR_NO_POP_FOR_PRIVKEY, |
| ERROR_UNSUPPORTED_POPOPRIVKEY_TYPE |
| } ErrorCode; |
| |
| const char * |
| CGITableFindValue(CGIVarTable *varTable, const char *key); |
| |
| void |
| spitOutHeaders(void) |
| { |
| printf("Content-type: text/html\n\n"); |
| } |
| |
| void |
| dumpRequest(CGIVarTable *varTable) |
| { |
| int i; |
| CGIVariable *var; |
| |
| printf("<table border=1 cellpadding=1 cellspacing=1 width=\"100%%\">\n"); |
| printf("<tr><td><b><center>Variable Name<center></b></td>" |
| "<td><b><center>Value</center></b></td></tr>\n"); |
| for (i = 0; i < varTable->numVars; i++) { |
| var = varTable->variables[i]; |
| printf("<tr><td><pre>%s</pre></td><td><pre>%s</pre></td></tr>\n", |
| var->name, var->value); |
| } |
| printf("</table>\n"); |
| } |
| |
| void |
| echo_request(CGIVarTable *varTable) |
| { |
| spitOutHeaders(); |
| printf("<html><head><title>CGI Echo Page</title></head>\n" |
| "<body><h1>Got the following request</h1>\n"); |
| dumpRequest(varTable); |
| printf("</body></html>"); |
| } |
| |
| void |
| processVariable(CGIVariable *var) |
| { |
| char *plusSign, *percentSign; |
| |
| /*First look for all of the '+' and convert them to spaces */ |
| plusSign = var->value; |
| while ((plusSign = strchr(plusSign, '+')) != NULL) { |
| *plusSign = ' '; |
| } |
| percentSign = var->value; |
| while ((percentSign = strchr(percentSign, '%')) != NULL) { |
| char string[3]; |
| int value; |
| |
| string[0] = percentSign[1]; |
| string[1] = percentSign[2]; |
| string[2] = '\0'; |
| |
| sscanf(string, "%x", &value); |
| *percentSign = (char)value; |
| memmove(&percentSign[1], &percentSign[3], 1 + strlen(&percentSign[3])); |
| } |
| } |
| |
| char * |
| parseNextVariable(CGIVarTable *varTable, char *form_output) |
| { |
| char *ampersand, *equal; |
| CGIVariable *var; |
| |
| if (varTable->numVars == varTable->numAlloc) { |
| CGIVariable **newArr = realloc(varTable->variables, |
| (varTable->numAlloc + DEFAULT_CGI_VARS) * sizeof(CGIVariable *)); |
| if (newArr == NULL) { |
| return NULL; |
| } |
| varTable->variables = newArr; |
| varTable->numAlloc += DEFAULT_CGI_VARS; |
| } |
| equal = strchr(form_output, '='); |
| if (equal == NULL) { |
| return NULL; |
| } |
| ampersand = strchr(equal, '&'); |
| if (ampersand == NULL) { |
| return NULL; |
| } |
| equal[0] = '\0'; |
| if (ampersand != NULL) { |
| ampersand[0] = '\0'; |
| } |
| var = malloc(sizeof(CGIVariable)); |
| var->name = form_output; |
| var->value = &equal[1]; |
| varTable->variables[varTable->numVars] = var; |
| varTable->numVars++; |
| processVariable(var); |
| return (ampersand != NULL) ? &ersand[1] : NULL; |
| } |
| |
| void |
| ParseInputVariables(CGIVarTable *varTable, char *form_output) |
| { |
| varTable->variables = malloc(sizeof(CGIVariable *) * DEFAULT_CGI_VARS); |
| varTable->numVars = 0; |
| varTable->numAlloc = DEFAULT_CGI_VARS; |
| while (form_output && form_output[0] != '\0') { |
| form_output = parseNextVariable(varTable, form_output); |
| } |
| } |
| |
| const char * |
| CGITableFindValue(CGIVarTable *varTable, const char *key) |
| { |
| const char *retVal = NULL; |
| int i; |
| |
| for (i = 0; i < varTable->numVars; i++) { |
| if (strcmp(varTable->variables[i]->name, key) == 0) { |
| retVal = varTable->variables[i]->value; |
| break; |
| } |
| } |
| return retVal; |
| } |
| |
| char * |
| passwordCallback(PK11SlotInfo *slot, PRBool retry, void *arg) |
| { |
| const char *passwd; |
| if (retry) { |
| return NULL; |
| } |
| passwd = CGITableFindValue((CGIVarTable *)arg, "dbPassword"); |
| if (passwd == NULL) { |
| return NULL; |
| } |
| return PORT_Strdup(passwd); |
| } |
| |
| ErrorCode |
| initNSS(CGIVarTable *varTable) |
| { |
| const char *nssDir; |
| PK11SlotInfo *keySlot; |
| SECStatus rv; |
| |
| nssDir = CGITableFindValue(varTable, "NSSDirectory"); |
| if (nssDir == NULL) { |
| missingVar = "NSSDirectory"; |
| return REQ_CGI_VAR_NOT_PRESENT; |
| } |
| rv = NSS_Init(nssDir); |
| if (rv != SECSuccess) { |
| return NSS_INIT_FAILED; |
| } |
| PK11_SetPasswordFunc(passwordCallback); |
| keySlot = PK11_GetInternalKeySlot(); |
| rv = PK11_Authenticate(keySlot, PR_FALSE, varTable); |
| PK11_FreeSlot(keySlot); |
| if (rv != SECSuccess) { |
| return AUTH_FAILED; |
| } |
| return NO_ERROR; |
| } |
| |
| void |
| dumpErrorMessage(ErrorCode errNum) |
| { |
| spitOutHeaders(); |
| printf("<html><head><title>Error</title></head><body><h1>Error processing " |
| "data</h1> Received the error %d<p>", |
| errNum); |
| if (errNum == REQ_CGI_VAR_NOT_PRESENT) { |
| printf("The missing variable is %s.", missingVar); |
| } |
| printf("<i>More useful information here in the future.</i></body></html>"); |
| } |
| |
| ErrorCode |
| initOldCertReq(CERTCertificateRequest *oldCertReq, |
| CERTName *subject, CERTSubjectPublicKeyInfo *spki) |
| { |
| PLArenaPool *poolp; |
| |
| poolp = oldCertReq->arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); |
| SEC_ASN1EncodeInteger(poolp, &oldCertReq->version, |
| SEC_CERTIFICATE_VERSION_3); |
| CERT_CopyName(poolp, &oldCertReq->subject, subject); |
| SECKEY_CopySubjectPublicKeyInfo(poolp, &oldCertReq->subjectPublicKeyInfo, |
| spki); |
| oldCertReq->attributes = NULL; |
| return NO_ERROR; |
| } |
| |
| ErrorCode |
| addExtensions(CERTCertificate *newCert, CRMFCertRequest *certReq) |
| { |
| int numExtensions, i; |
| void *extHandle; |
| ErrorCode rv = NO_ERROR; |
| CRMFCertExtension *ext; |
| SECStatus srv; |
| |
| numExtensions = CRMF_CertRequestGetNumberOfExtensions(certReq); |
| if (numExtensions == 0) { |
| /* No extensions to add */ |
| return NO_ERROR; |
| } |
| extHandle = CERT_StartCertExtensions(newCert); |
| if (extHandle == NULL) { |
| rv = COULD_NOT_START_EXTENSIONS; |
| goto loser; |
| } |
| for (i = 0; i < numExtensions; i++) { |
| ext = CRMF_CertRequestGetExtensionAtIndex(certReq, i); |
| if (ext == NULL) { |
| rv = ERROR_RETRIEVING_EXT_FROM_REQ; |
| } |
| srv = CERT_AddExtension(extHandle, CRMF_CertExtensionGetOidTag(ext), |
| CRMF_CertExtensionGetValue(ext), |
| CRMF_CertExtensionGetIsCritical(ext), PR_FALSE); |
| if (srv != SECSuccess) { |
| rv = ERROR_ADDING_EXT_TO_CERT; |
| } |
| } |
| srv = CERT_FinishExtensions(extHandle); |
| if (srv != SECSuccess) { |
| rv = ERROR_ENDING_EXTENSIONS; |
| goto loser; |
| } |
| return NO_ERROR; |
| loser: |
| return rv; |
| } |
| |
| void |
| writeOutItem(const char *filePath, SECItem *der) |
| { |
| PRFileDesc *outfile; |
| |
| outfile = PR_Open(filePath, |
| PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, |
| 0666); |
| PR_Write(outfile, der->data, der->len); |
| PR_Close(outfile); |
| } |
| |
| ErrorCode |
| createNewCert(CERTCertificate **issuedCert, CERTCertificateRequest *oldCertReq, |
| CRMFCertReqMsg *currReq, CRMFCertRequest *certReq, |
| CERTCertificate *issuerCert, CGIVarTable *varTable) |
| { |
| CERTCertificate *newCert = NULL; |
| CERTValidity *validity; |
| PRExplodedTime printableTime; |
| PRTime now, after; |
| ErrorCode rv = NO_ERROR; |
| SECKEYPrivateKey *issuerPrivKey; |
| SECItem derCert = { 0 }; |
| SECOidTag signTag; |
| SECStatus srv; |
| long version; |
| |
| now = PR_Now(); |
| PR_ExplodeTime(now, PR_GMTParameters, &printableTime); |
| printableTime.tm_month += 9; |
| after = PR_ImplodeTime(&printableTime); |
| validity = CERT_CreateValidity(now, after); |
| newCert = *issuedCert = |
| CERT_CreateCertificate(rand(), &(issuerCert->subject), validity, |
| oldCertReq); |
| if (newCert == NULL) { |
| rv = ERROR_CREATING_NEW_CERTIFICATE; |
| goto loser; |
| } |
| rv = addExtensions(newCert, certReq); |
| if (rv != NO_ERROR) { |
| goto loser; |
| } |
| issuerPrivKey = PK11_FindKeyByAnyCert(issuerCert, varTable); |
| if (issuerPrivKey == NULL) { |
| rv = COULD_NOT_FIND_ISSUER_PRIVATE_KEY; |
| } |
| signTag = SEC_GetSignatureAlgorithmOidTag(issuerPrivatekey->keytype, |
| SEC_OID_UNKNOWN); |
| if (signTag == SEC_OID_UNKNOWN) { |
| rv = UNSUPPORTED_SIGN_OPERATION_FOR_ISSUER; |
| goto loser; |
| } |
| srv = SECOID_SetAlgorithmID(newCert->arena, &newCert->signature, |
| signTag, 0); |
| if (srv != SECSuccess) { |
| rv = ERROR_SETTING_SIGN_ALG; |
| goto loser; |
| } |
| srv = CRMF_CertRequestGetCertTemplateVersion(certReq, &version); |
| if (srv != SECSuccess) { |
| /* No version included in the request */ |
| *(newCert->version.data) = SEC_CERTIFICATE_VERSION_3; |
| } else { |
| SECITEM_FreeItem(&newCert->version, PR_FALSE); |
| SEC_ASN1EncodeInteger(newCert->arena, &newCert->version, version); |
| } |
| SEC_ASN1EncodeItem(newCert->arena, &derCert, newCert, |
| CERT_CertificateTemplate); |
| if (derCert.data == NULL) { |
| rv = ERROR_ENCODING_NEW_CERT; |
| goto loser; |
| } |
| srv = SEC_DerSignData(newCert->arena, &(newCert->derCert), derCert.data, |
| derCert.len, issuerPrivKey, signTag); |
| if (srv != SECSuccess) { |
| rv = ERROR_SIGNING_NEW_CERT; |
| goto loser; |
| } |
| #ifdef WRITE_OUT_RESPONSE |
| writeOutItem("newcert.der", &newCert->derCert); |
| #endif |
| return NO_ERROR; |
| loser: |
| *issuedCert = NULL; |
| if (newCert) { |
| CERT_DestroyCertificate(newCert); |
| } |
| return rv; |
| } |
| |
| void |
| formatCMMFResponse(char *nickname, char *base64Response) |
| { |
| char *currLine, *nextLine; |
| |
| printf("var retVal = crypto.importUserCertificates(\"%s\",\n", nickname); |
| currLine = base64Response; |
| while (1) { |
| nextLine = strchr(currLine, '\n'); |
| if (nextLine == NULL) { |
| /* print out the last line here. */ |
| printf("\"%s\",\n", currLine); |
| break; |
| } |
| nextLine[0] = '\0'; |
| printf("\"%s\\n\"+\n", currLine); |
| currLine = nextLine + 1; |
| } |
| printf("true);\n" |
| "if(retVal == '') {\n" |
| "\tdocument.write(\"<h1>New Certificate Successfully Imported.</h1>\");\n" |
| "} else {\n" |
| "\tdocument.write(\"<h2>Unable to import New Certificate</h2>\");\n" |
| "\tdocument.write(\"crypto.importUserCertificates returned <b>\");\n" |
| "\tdocument.write(retVal);\n" |
| "\tdocument.write(\"</b>\");\n" |
| "}\n"); |
| } |
| |
| void |
| spitOutCMMFResponse(char *nickname, char *base64Response) |
| { |
| spitOutHeaders(); |
| printf("<html>\n<head>\n<title>CMMF Resonse Page</title>\n</head>\n\n" |
| "<body><h1>CMMF Response Page</h1>\n" |
| "<script language=\"JavaScript\">\n" |
| "<!--\n"); |
| formatCMMFResponse(nickname, base64Response); |
| printf("// -->\n" |
| "</script>\n</body>\n</html>"); |
| } |
| |
| char * |
| getNickname(CERTCertificate *cert) |
| { |
| char *nickname; |
| |
| if (cert->nickname != NULL) { |
| return cert->nickname; |
| } |
| nickname = CERT_GetCommonName(&cert->subject); |
| if (nickname != NULL) { |
| return nickname; |
| } |
| return CERT_NameToAscii(&cert->subject); |
| } |
| |
| ErrorCode |
| createCMMFResponse(CertResponseInfo *issuedCerts, int numCerts, |
| CERTCertificate *issuerCert, char **base64der) |
| { |
| CMMFCertRepContent *certRepContent = NULL; |
| ErrorCode rv = NO_ERROR; |
| CMMFCertResponse **responses, *currResponse; |
| CERTCertList *caList; |
| int i; |
| SECStatus srv; |
| PLArenaPool *poolp; |
| SECItem *der; |
| |
| certRepContent = CMMF_CreateCertRepContent(); |
| if (certRepContent == NULL) { |
| rv = ERROR_CREATING_CERT_REP_CONTENT; |
| goto loser; |
| } |
| responses = PORT_NewArray(CMMFCertResponse *, numCerts); |
| if (responses == NULL) { |
| rv = OUT_OF_MEMORY; |
| goto loser; |
| } |
| for (i = 0; i < numCerts; i++) { |
| responses[i] = currResponse = |
| CMMF_CreateCertResponse(issuedCerts[i].certReqID); |
| if (currResponse == NULL) { |
| rv = ERROR_CREATING_SINGLE_CERT_RESPONSE; |
| goto loser; |
| } |
| CMMF_CertResponseSetPKIStatusInfoStatus(currResponse, cmmfGranted); |
| CMMF_CertResponseSetCertificate(currResponse, issuedCerts[i].cert); |
| } |
| srv = CMMF_CertRepContentSetCertResponses(certRepContent, responses, |
| numCerts); |
| if (srv != SECSuccess) { |
| rv = ERROR_SETTING_CERT_RESPONSES; |
| goto loser; |
| } |
| caList = CERT_NewCertList(); |
| if (caList == NULL) { |
| rv = ERROR_CREATING_CA_LIST; |
| goto loser; |
| } |
| srv = CERT_AddCertToListTail(caList, issuerCert); |
| if (srv != SECSuccess) { |
| rv = ERROR_ADDING_ISSUER_TO_CA_LIST; |
| goto loser; |
| } |
| srv = CMMF_CertRepContentSetCAPubs(certRepContent, caList); |
| CERT_DestroyCertList(caList); |
| poolp = PORT_NewArena(1024); |
| der = SEC_ASN1EncodeItem(poolp, NULL, certRepContent, |
| CMMFCertRepContentTemplate); |
| if (der == NULL) { |
| rv = ERROR_ENCODING_CERT_REP_CONTENT; |
| goto loser; |
| } |
| #ifdef WRITE_OUT_RESPONSE |
| writeOutItem("CertRepContent.der", der); |
| #endif |
| *base64der = BTOA_DataToAscii(der->data, der->len); |
| return NO_ERROR; |
| loser: |
| return rv; |
| } |
| |
| ErrorCode |
| issueCerts(CertResponseInfo *issuedCerts, int numCerts, |
| CERTCertificate *issuerCert) |
| { |
| ErrorCode rv; |
| char *base64Response; |
| |
| rv = createCMMFResponse(issuedCerts, numCerts, issuerCert, &base64Response); |
| if (rv != NO_ERROR) { |
| goto loser; |
| } |
| spitOutCMMFResponse(getNickname(issuedCerts[0].cert), base64Response); |
| return NO_ERROR; |
| loser: |
| return rv; |
| } |
| |
| ErrorCode |
| verifySignature(CGIVarTable *varTable, CRMFCertReqMsg *currReq, |
| CRMFCertRequest *certReq, CERTCertificate *newCert) |
| { |
| SECStatus srv; |
| ErrorCode rv = NO_ERROR; |
| CRMFPOPOSigningKey *signKey = NULL; |
| SECAlgorithmID *algID = NULL; |
| SECItem *signature = NULL; |
| SECKEYPublicKey *pubKey = NULL; |
| SECItem *reqDER = NULL; |
| |
| srv = CRMF_CertReqMsgGetPOPOSigningKey(currReq, &signKey); |
| if (srv != SECSuccess || signKey == NULL) { |
| rv = ERROR_RETRIEVING_POP_SIGN_KEY; |
| goto loser; |
| } |
| algID = CRMF_POPOSigningKeyGetAlgID(signKey); |
| if (algID == NULL) { |
| rv = ERROR_RETRIEVING_ALG_ID_FROM_SIGN_KEY; |
| goto loser; |
| } |
| signature = CRMF_POPOSigningKeyGetSignature(signKey); |
| if (signature == NULL) { |
| rv = ERROR_RETRIEVING_SIGNATURE_FROM_POP_SIGN_KEY; |
| goto loser; |
| } |
| /* Make the length the number of bytes instead of bits */ |
| signature->len = (signature->len + 7) / 8; |
| pubKey = CERT_ExtractPublicKey(newCert); |
| if (pubKey == NULL) { |
| rv = ERROR_RETRIEVING_PUB_KEY_FROM_NEW_CERT; |
| goto loser; |
| } |
| reqDER = SEC_ASN1EncodeItem(NULL, NULL, certReq, CRMFCertRequestTemplate); |
| if (reqDER == NULL) { |
| rv = ERROR_ENCODING_CERT_REQ_FOR_POP; |
| goto loser; |
| } |
| srv = VFY_VerifyDataWithAlgorithmID(reqDER->data, reqDER->len, pubKey, |
| signature, &algID->algorithm, NULL, varTable); |
| if (srv != SECSuccess) { |
| rv = ERROR_VERIFYING_SIGNATURE_POP; |
| goto loser; |
| } |
| /* Fall thru in successfull case. */ |
| loser: |
| if (pubKey != NULL) { |
| SECKEY_DestroyPublicKey(pubKey); |
| } |
| if (reqDER != NULL) { |
| SECITEM_FreeItem(reqDER, PR_TRUE); |
| } |
| if (signature != NULL) { |
| SECITEM_FreeItem(signature, PR_TRUE); |
| } |
| if (algID != NULL) { |
| SECOID_DestroyAlgorithmID(algID, PR_TRUE); |
| } |
| if (signKey != NULL) { |
| CRMF_DestroyPOPOSigningKey(signKey); |
| } |
| return rv; |
| } |
| |
| ErrorCode |
| doChallengeResponse(CGIVarTable *varTable, CRMFCertReqMsg *currReq, |
| CRMFCertRequest *certReq, CERTCertificate *newCert, |
| ChallengeCreationInfo *challs, int *numChall) |
| { |
| CRMFPOPOPrivKey *privKey = NULL; |
| CRMFPOPOPrivKeyChoice privKeyChoice; |
| SECStatus srv; |
| ErrorCode rv = NO_ERROR; |
| |
| srv = CRMF_CertReqMsgGetPOPKeyEncipherment(currReq, &privKey); |
| if (srv != SECSuccess || privKey == NULL) { |
| rv = ERROR_GETTING_KEY_ENCIPHERMENT; |
| goto loser; |
| } |
| privKeyChoice = CRMF_POPOPrivKeyGetChoice(privKey); |
| CRMF_DestroyPOPOPrivKey(privKey); |
| switch (privKeyChoice) { |
| case crmfSubsequentMessage: |
| challs = &challs[*numChall]; |
| challs->random = rand(); |
| challs->pubKey = CERT_ExtractPublicKey(newCert); |
| if (challs->pubKey == NULL) { |
| rv = |
| ERROR_RETRIEVING_PUB_KEY_FOR_CHALL; |
| goto loser; |
| } |
| (*numChall)++; |
| rv = DO_CHALLENGE_RESPONSE; |
| break; |
| case crmfThisMessage: |
| /* There'd better be a PKIArchiveControl in this message */ |
| if (!CRMF_CertRequestIsControlPresent(certReq, |
| crmfPKIArchiveOptionsControl)) { |
| rv = |
| ERROR_NO_POP_FOR_PRIVKEY; |
| goto loser; |
| } |
| break; |
| default: |
| rv = ERROR_UNSUPPORTED_POPOPRIVKEY_TYPE; |
| goto loser; |
| } |
| loser: |
| return rv; |
| } |
| |
| ErrorCode |
| doProofOfPossession(CGIVarTable *varTable, CRMFCertReqMsg *currReq, |
| CRMFCertRequest *certReq, CERTCertificate *newCert, |
| ChallengeCreationInfo *challs, int *numChall) |
| { |
| CRMFPOPChoice popChoice; |
| ErrorCode rv = NO_ERROR; |
| |
| popChoice = CRMF_CertReqMsgGetPOPType(currReq); |
| if (popChoice == crmfNoPOPChoice) { |
| rv = NO_POP_FOR_REQUEST; |
| goto loser; |
| } |
| switch (popChoice) { |
| case crmfSignature: |
| rv = verifySignature(varTable, currReq, certReq, newCert); |
| break; |
| case crmfKeyEncipherment: |
| rv = doChallengeResponse(varTable, currReq, certReq, newCert, |
| challs, numChall); |
| break; |
| case crmfRAVerified: |
| case crmfKeyAgreement: |
| default: |
| rv = UNSUPPORTED_POP; |
| goto loser; |
| } |
| loser: |
| return rv; |
| } |
| |
| void |
| convertB64ToJS(char *base64) |
| { |
| int i; |
| |
| for (i = 0; base64[i] != '\0'; i++) { |
| if (base64[i] == '\n') { |
| printf("\\n"); |
| } else { |
| printf("%c", base64[i]); |
| } |
| } |
| } |
| |
| void |
| formatChallenge(char *chall64, char *certRepContentDER, |
| ChallengeCreationInfo *challInfo, int numChalls) |
| { |
| printf("function respondToChallenge() {\n" |
| " var chalForm = document.chalForm;\n\n" |
| " chalForm.CertRepContent.value = '"); |
| convertB64ToJS(certRepContentDER); |
| printf("';\n" |
| " chalForm.ChallResponse.value = crypto.popChallengeResponse('"); |
| convertB64ToJS(chall64); |
| printf("');\n" |
| " chalForm.submit();\n" |
| "}\n"); |
| } |
| |
| void |
| spitOutChallenge(char *chall64, char *certRepContentDER, |
| ChallengeCreationInfo *challInfo, int numChalls, |
| char *nickname) |
| { |
| int i; |
| |
| spitOutHeaders(); |
| printf("<html>\n" |
| "<head>\n" |
| "<title>Challenge Page</title>\n" |
| "<script language=\"JavaScript\">\n" |
| "<!--\n"); |
| /* The JavaScript function actually gets defined within |
| * this function call |
| */ |
| formatChallenge(chall64, certRepContentDER, challInfo, numChalls); |
| printf("// -->\n" |
| "</script>\n" |
| "</head>\n" |
| "<body onLoad='respondToChallenge()'>\n" |
| "<h1>Cartman is now responding to the Challenge " |
| "presented by the CGI</h1>\n" |
| "<form action='crmfcgi' method='post' name='chalForm'>\n" |
| "<input type='hidden' name=CertRepContent value=''>\n" |
| "<input type='hidden' name=ChallResponse value=''>\n"); |
| for (i = 0; i < numChalls; i++) { |
| printf("<input type='hidden' name='chal%d' value='%d'>\n", |
| i + 1, challInfo[i].random); |
| } |
| printf("<input type='hidden' name='nickname' value='%s'>\n", nickname); |
| printf("</form>\n</body>\n</html>"); |
| } |
| |
| ErrorCode |
| issueChallenge(CertResponseInfo *issuedCerts, int numCerts, |
| ChallengeCreationInfo *challInfo, int numChalls, |
| CERTCertificate *issuer, CGIVarTable *varTable) |
| { |
| ErrorCode rv = NO_ERROR; |
| CMMFPOPODecKeyChallContent *chalContent = NULL; |
| int i; |
| SECStatus srv; |
| PLArenaPool *poolp; |
| CERTGeneralName *genName; |
| SECItem *challDER = NULL; |
| char *chall64, *certRepContentDER; |
| |
| rv = createCMMFResponse(issuedCerts, numCerts, issuer, |
| &certRepContentDER); |
| if (rv != NO_ERROR) { |
| goto loser; |
| } |
| chalContent = CMMF_CreatePOPODecKeyChallContent(); |
| if (chalContent == NULL) { |
| rv = ERROR_CREATING_EMPTY_CHAL_CONTENT; |
| goto loser; |
| } |
| poolp = PORT_NewArena(1024); |
| if (poolp == NULL) { |
| rv = OUT_OF_MEMORY; |
| goto loser; |
| } |
| genName = CERT_GetCertificateNames(issuer, poolp); |
| if (genName == NULL) { |
| rv = ERROR_EXTRACTING_GEN_NAME_FROM_ISSUER; |
| goto loser; |
| } |
| for (i = 0; i < numChalls; i++) { |
| srv = CMMF_POPODecKeyChallContentSetNextChallenge(chalContent, |
| challInfo[i].random, |
| genName, |
| challInfo[i].pubKey, |
| varTable); |
| SECKEY_DestroyPublicKey(challInfo[i].pubKey); |
| if (srv != SECSuccess) { |
| rv = ERROR_SETTING_CHALLENGE; |
| goto loser; |
| } |
| } |
| challDER = SEC_ASN1EncodeItem(NULL, NULL, chalContent, |
| CMMFPOPODecKeyChallContentTemplate); |
| if (challDER == NULL) { |
| rv = ERROR_ENCODING_CHALL; |
| goto loser; |
| } |
| chall64 = BTOA_DataToAscii(challDER->data, challDER->len); |
| SECITEM_FreeItem(challDER, PR_TRUE); |
| if (chall64 == NULL) { |
| rv = ERROR_CONVERTING_CHALL_TO_BASE64; |
| goto loser; |
| } |
| spitOutChallenge(chall64, certRepContentDER, challInfo, numChalls, |
| getNickname(issuedCerts[0].cert)); |
| loser: |
| return rv; |
| } |
| |
| ErrorCode |
| processRequest(CGIVarTable *varTable) |
| { |
| CERTCertDBHandle *certdb; |
| SECKEYKeyDBHandle *keydb; |
| CRMFCertReqMessages *certReqs = NULL; |
| const char *crmfReq; |
| const char *caNickname; |
| CERTCertificate *caCert = NULL; |
| CertResponseInfo *issuedCerts = NULL; |
| CERTSubjectPublicKeyInfo spki = { 0 }; |
| ErrorCode rv = NO_ERROR; |
| PRBool doChallengeResponse = PR_FALSE; |
| SECItem der = { 0 }; |
| SECStatus srv; |
| CERTCertificateRequest oldCertReq = { 0 }; |
| CRMFCertReqMsg **reqMsgs = NULL, *currReq = NULL; |
| CRMFCertRequest **reqs = NULL, *certReq = NULL; |
| CERTName subject = { 0 }; |
| int numReqs, i; |
| ChallengeCreationInfo *challInfo = NULL; |
| int numChalls = 0; |
| |
| certdb = CERT_GetDefaultCertDB(); |
| keydb = SECKEY_GetDefaultKeyDB(); |
| crmfReq = CGITableFindValue(varTable, "CRMFRequest"); |
| if (crmfReq == NULL) { |
| rv = CGI_VAR_MISSING; |
| missingVar = "CRMFRequest"; |
| goto loser; |
| } |
| caNickname = CGITableFindValue(varTable, "CANickname"); |
| if (caNickname == NULL) { |
| rv = CGI_VAR_MISSING; |
| missingVar = "CANickname"; |
| goto loser; |
| } |
| caCert = CERT_FindCertByNickname(certdb, caNickname); |
| if (caCert == NULL) { |
| rv = COULD_NOT_FIND_CA; |
| goto loser; |
| } |
| srv = ATOB_ConvertAsciiToItem(&der, crmfReq); |
| if (srv != SECSuccess) { |
| rv = BAD_ASCII_FOR_REQ; |
| goto loser; |
| } |
| certReqs = CRMF_CreateCertReqMessagesFromDER(der.data, der.len); |
| SECITEM_FreeItem(&der, PR_FALSE); |
| if (certReqs == NULL) { |
| rv = COULD_NOT_DECODE_REQS; |
| goto loser; |
| } |
| numReqs = CRMF_CertReqMessagesGetNumMessages(certReqs); |
| issuedCerts = PORT_ZNewArray(CertResponseInfo, numReqs); |
| challInfo = PORT_ZNewArray(ChallengeCreationInfo, numReqs); |
| if (issuedCerts == NULL || challInfo == NULL) { |
| rv = OUT_OF_MEMORY; |
| goto loser; |
| } |
| reqMsgs = PORT_ZNewArray(CRMFCertReqMsg *, numReqs); |
| reqs = PORT_ZNewArray(CRMFCertRequest *, numReqs); |
| if (reqMsgs == NULL || reqs == NULL) { |
| rv = OUT_OF_MEMORY; |
| goto loser; |
| } |
| for (i = 0; i < numReqs; i++) { |
| currReq = reqMsgs[i] = |
| CRMF_CertReqMessagesGetCertReqMsgAtIndex(certReqs, i); |
| if (currReq == NULL) { |
| rv = ERROR_RETRIEVING_REQUEST_MSG; |
| goto loser; |
| } |
| certReq = reqs[i] = CRMF_CertReqMsgGetCertRequest(currReq); |
| if (certReq == NULL) { |
| rv = ERROR_RETRIEVING_CERT_REQUEST; |
| goto loser; |
| } |
| srv = CRMF_CertRequestGetCertTemplateSubject(certReq, &subject); |
| if (srv != SECSuccess) { |
| rv = ERROR_RETRIEVING_SUBJECT_FROM_REQ; |
| goto loser; |
| } |
| srv = CRMF_CertRequestGetCertTemplatePublicKey(certReq, &spki); |
| if (srv != SECSuccess) { |
| rv = ERROR_RETRIEVING_PUBLIC_KEY_FROM_REQ; |
| goto loser; |
| } |
| rv = initOldCertReq(&oldCertReq, &subject, &spki); |
| if (rv != NO_ERROR) { |
| goto loser; |
| } |
| rv = createNewCert(&issuedCerts[i].cert, &oldCertReq, currReq, certReq, |
| caCert, varTable); |
| if (rv != NO_ERROR) { |
| goto loser; |
| } |
| rv = doProofOfPossession(varTable, currReq, certReq, issuedCerts[i].cert, |
| challInfo, &numChalls); |
| if (rv != NO_ERROR) { |
| if (rv == DO_CHALLENGE_RESPONSE) { |
| doChallengeResponse = PR_TRUE; |
| } else { |
| goto loser; |
| } |
| } |
| CRMF_CertReqMsgGetID(currReq, &issuedCerts[i].certReqID); |
| CRMF_DestroyCertReqMsg(currReq); |
| CRMF_DestroyCertRequest(certReq); |
| } |
| if (doChallengeResponse) { |
| rv = issueChallenge(issuedCerts, numReqs, challInfo, numChalls, caCert, |
| varTable); |
| } else { |
| rv = issueCerts(issuedCerts, numReqs, caCert); |
| } |
| loser: |
| if (certReqs != NULL) { |
| CRMF_DestroyCertReqMessages(certReqs); |
| } |
| return rv; |
| } |
| |
| ErrorCode |
| processChallengeResponse(CGIVarTable *varTable, const char *certRepContent) |
| { |
| SECItem binDER = { 0 }; |
| SECStatus srv; |
| ErrorCode rv = NO_ERROR; |
| const char *clientResponse; |
| const char *formChalValue; |
| const char *nickname; |
| CMMFPOPODecKeyRespContent *respContent = NULL; |
| int numResponses, i; |
| long curResponse, expectedResponse; |
| char cgiChalVar[10]; |
| #ifdef WRITE_OUT_RESPONSE |
| SECItem certRepBinDER = { 0 }; |
| |
| ATOB_ConvertAsciiToItem(&certRepBinDER, certRepContent); |
| writeOutItem("challCertRepContent.der", &certRepBinDER); |
| PORT_Free(certRepBinDER.data); |
| #endif |
| clientResponse = CGITableFindValue(varTable, "ChallResponse"); |
| if (clientResponse == NULL) { |
| rv = REQ_CGI_VAR_NOT_PRESENT; |
| missingVar = "ChallResponse"; |
| goto loser; |
| } |
| srv = ATOB_ConvertAsciiToItem(&binDER, clientResponse); |
| if (srv != SECSuccess) { |
| rv = ERROR_CONVERTING_RESP_FROM_CHALL_TO_BIN; |
| goto loser; |
| } |
| respContent = CMMF_CreatePOPODecKeyRespContentFromDER(binDER.data, |
| binDER.len); |
| SECITEM_FreeItem(&binDER, PR_FALSE); |
| binDER.data = NULL; |
| if (respContent == NULL) { |
| rv = ERROR_CREATING_KEY_RESP_FROM_DER; |
| goto loser; |
| } |
| numResponses = CMMF_POPODecKeyRespContentGetNumResponses(respContent); |
| for (i = 0; i < numResponses; i++) { |
| srv = CMMF_POPODecKeyRespContentGetResponse(respContent, i, &curResponse); |
| if (srv != SECSuccess) { |
| rv = ERROR_RETRIEVING_CLIENT_RESPONSE_TO_CHALLENGE; |
| goto loser; |
| } |
| sprintf(cgiChalVar, "chal%d", i + 1); |
| formChalValue = CGITableFindValue(varTable, cgiChalVar); |
| if (formChalValue == NULL) { |
| rv = REQ_CGI_VAR_NOT_PRESENT; |
| missingVar = strdup(cgiChalVar); |
| goto loser; |
| } |
| sscanf(formChalValue, "%ld", &expectedResponse); |
| if (expectedResponse != curResponse) { |
| rv = ERROR_RETURNED_CHALL_NOT_VALUE_EXPECTED; |
| goto loser; |
| } |
| } |
| nickname = CGITableFindValue(varTable, "nickname"); |
| if (nickname == NULL) { |
| rv = REQ_CGI_VAR_NOT_PRESENT; |
| missingVar = "nickname"; |
| goto loser; |
| } |
| spitOutCMMFResponse(nickname, certRepContent); |
| loser: |
| if (respContent != NULL) { |
| CMMF_DestroyPOPODecKeyRespContent(respContent); |
| } |
| return rv; |
| } |
| |
| int |
| main() |
| { |
| char *form_output = NULL; |
| int form_output_len, form_output_used; |
| CGIVarTable varTable = { 0 }; |
| ErrorCode errNum = 0; |
| char *certRepContent; |
| |
| #ifdef ATTACH_CGI |
| /* Put an ifinite loop in here so I can attach to |
| * the process after the process is spun off |
| */ |
| { |
| int stupid = 1; |
| while (stupid) |
| ; |
| } |
| #endif |
| |
| form_output_used = 0; |
| srand(time(NULL)); |
| while (feof(stdin) == 0) { |
| if (form_output == NULL) { |
| form_output = PORT_NewArray(char, DEFAULT_ALLOC_SIZE + 1); |
| form_output_len = DEFAULT_ALLOC_SIZE; |
| } else if ((form_output_used + DEFAULT_ALLOC_SIZE) >= form_output_len) { |
| form_output_len += DEFAULT_ALLOC_SIZE; |
| form_output = PORT_Realloc(form_output, form_output_len + 1); |
| } |
| form_output_used += fread(&form_output[form_output_used], sizeof(char), |
| DEFAULT_ALLOC_SIZE, stdin); |
| } |
| ParseInputVariables(&varTable, form_output); |
| certRepContent = CGITableFindValue(&varTable, "CertRepContent"); |
| if (certRepContent == NULL) { |
| errNum = initNSS(&varTable); |
| if (errNum != 0) { |
| goto loser; |
| } |
| errNum = processRequest(&varTable); |
| } else { |
| errNum = processChallengeResponse(&varTable, certRepContent); |
| } |
| if (errNum != NO_ERROR) { |
| goto loser; |
| } |
| goto done; |
| loser: |
| dumpErrorMessage(errNum); |
| done: |
| free(form_output); |
| return 0; |
| } |