/** * XMLSec library * * X509 support * * * This is free software; see Copyright file in the source * distribution for preciese wording. * * Copyright (c) 2003 America Online, Inc. All rights reserved. */ #include "globals.h" #ifndef XMLSEC_NO_X509 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /************************************************************************** * * Internal NSS X509 store CTX * *************************************************************************/ typedef struct _xmlSecNssX509StoreCtx xmlSecNssX509StoreCtx, *xmlSecNssX509StoreCtxPtr; struct _xmlSecNssX509StoreCtx { CERTCertList* certsList; /* just keeping a reference to destroy later */ }; /**************************************************************************** * * xmlSecNssKeyDataStoreX509Id: * * xmlSecNssX509StoreCtx is located after xmlSecTransform * ***************************************************************************/ #define xmlSecNssX509StoreGetCtx(store) \ ((xmlSecNssX509StoreCtxPtr)(((xmlSecByte*)(store)) + \ sizeof(xmlSecKeyDataStoreKlass))) #define xmlSecNssX509StoreSize \ (sizeof(xmlSecKeyDataStoreKlass) + sizeof(xmlSecNssX509StoreCtx)) static int xmlSecNssX509StoreInitialize (xmlSecKeyDataStorePtr store); static void xmlSecNssX509StoreFinalize (xmlSecKeyDataStorePtr store); static int xmlSecNssX509NameStringRead (xmlSecByte **str, int *strLen, xmlSecByte *res, int resLen, xmlSecByte delim, int ingoreTrailingSpaces); static xmlSecByte * xmlSecNssX509NameRead (xmlSecByte *str, int len); static void xmlSecNssNumToItem(SECItem *it, unsigned long num); static xmlSecKeyDataStoreKlass xmlSecNssX509StoreKlass = { sizeof(xmlSecKeyDataStoreKlass), xmlSecNssX509StoreSize, /* data */ xmlSecNameX509Store, /* const xmlChar* name; */ /* constructors/destructor */ xmlSecNssX509StoreInitialize, /* xmlSecKeyDataStoreInitializeMethod initialize; */ xmlSecNssX509StoreFinalize, /* xmlSecKeyDataStoreFinalizeMethod finalize; */ /* reserved for the future */ NULL, /* void* reserved0; */ NULL, /* void* reserved1; */ }; static CERTCertificate* xmlSecNssX509FindCert(xmlChar *subjectName, xmlChar *issuerName, xmlChar *issuerSerial, xmlChar *ski); /** * xmlSecNssX509StoreGetKlass: * * The NSS X509 certificates key data store klass. * * Returns: pointer to NSS X509 certificates key data store klass. */ xmlSecKeyDataStoreId xmlSecNssX509StoreGetKlass(void) { return(&xmlSecNssX509StoreKlass); } /** * xmlSecNssX509StoreFindCert: * @store: the pointer to X509 key data store klass. * @subjectName: the desired certificate name. * @issuerName: the desired certificate issuer name. * @issuerSerial: the desired certificate issuer serial number. * @ski: the desired certificate SKI. * @keyInfoCtx: the pointer to element processing context. * * Searches @store for a certificate that matches given criteria. * * Returns: pointer to found certificate or NULL if certificate is not found * or an error occurs. */ CERTCertificate * xmlSecNssX509StoreFindCert(xmlSecKeyDataStorePtr store, xmlChar *subjectName, xmlChar *issuerName, xmlChar *issuerSerial, xmlChar *ski, xmlSecKeyInfoCtx* keyInfoCtx) { xmlSecNssX509StoreCtxPtr ctx; xmlSecAssert2(xmlSecKeyDataStoreCheckId(store, xmlSecNssX509StoreId), NULL); xmlSecAssert2(keyInfoCtx != NULL, NULL); ctx = xmlSecNssX509StoreGetCtx(store); xmlSecAssert2(ctx != NULL, NULL); return(xmlSecNssX509FindCert(subjectName, issuerName, issuerSerial, ski)); } /** * xmlSecNssX509StoreVerify: * @store: the pointer to X509 key data store klass. * @certs: the untrusted certificates stack. * @keyInfoCtx: the pointer to element processing context. * * Verifies @certs list. * * Returns: pointer to the first verified certificate from @certs. */ CERTCertificate * xmlSecNssX509StoreVerify(xmlSecKeyDataStorePtr store, CERTCertList* certs, xmlSecKeyInfoCtx* keyInfoCtx) { xmlSecNssX509StoreCtxPtr ctx; CERTCertListNode* head; CERTCertificate* cert = NULL; CERTCertListNode* head1; CERTCertificate* cert1 = NULL; SECStatus status = SECFailure; int64 timeboundary; int64 tmp1, tmp2; xmlSecAssert2(xmlSecKeyDataStoreCheckId(store, xmlSecNssX509StoreId), NULL); xmlSecAssert2(certs != NULL, NULL); xmlSecAssert2(keyInfoCtx != NULL, NULL); ctx = xmlSecNssX509StoreGetCtx(store); xmlSecAssert2(ctx != NULL, NULL); for (head = CERT_LIST_HEAD(certs); !CERT_LIST_END(head, certs); head = CERT_LIST_NEXT(head)) { cert = head->cert; if(keyInfoCtx->certsVerificationTime > 0) { /* convert the time since epoch in seconds to microseconds */ LL_UI2L(timeboundary, keyInfoCtx->certsVerificationTime); tmp1 = (int64)PR_USEC_PER_SEC; tmp2 = timeboundary; LL_MUL(timeboundary, tmp1, tmp2); } else { timeboundary = PR_Now(); } /* if cert is the issuer of any other cert in the list, then it is * to be skipped */ for (head1 = CERT_LIST_HEAD(certs); !CERT_LIST_END(head1, certs); head1 = CERT_LIST_NEXT(head1)) { cert1 = head1->cert; if (cert1 == cert) { continue; } if (SECITEM_CompareItem(&cert1->derIssuer, &cert->derSubject) == SECEqual) { break; } } if (!CERT_LIST_END(head1, certs)) { continue; } status = CERT_VerifyCertificate(CERT_GetDefaultCertDB(), cert, PR_FALSE, (SECCertificateUsage)0, timeboundary , NULL, NULL, NULL); if (status == SECSuccess) { break; } } if (status == SECSuccess) { return (cert); } switch(PORT_GetError()) { case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE: case SEC_ERROR_CA_CERT_INVALID: case SEC_ERROR_UNKNOWN_SIGNER: xmlSecError(XMLSEC_ERRORS_HERE, xmlSecErrorsSafeString(xmlSecKeyDataStoreGetName(store)), NULL, XMLSEC_ERRORS_R_CERT_ISSUER_FAILED, "cert with subject name %s could not be verified because the issuer's cert is expired/invalid or not found", cert->subjectName); break; case SEC_ERROR_EXPIRED_CERTIFICATE: xmlSecError(XMLSEC_ERRORS_HERE, xmlSecErrorsSafeString(xmlSecKeyDataStoreGetName(store)), NULL, XMLSEC_ERRORS_R_CERT_HAS_EXPIRED, "cert with subject name %s has expired", cert->subjectName); break; case SEC_ERROR_REVOKED_CERTIFICATE: xmlSecError(XMLSEC_ERRORS_HERE, xmlSecErrorsSafeString(xmlSecKeyDataStoreGetName(store)), NULL, XMLSEC_ERRORS_R_CERT_REVOKED, "cert with subject name %s has been revoked", cert->subjectName); break; default: xmlSecError(XMLSEC_ERRORS_HERE, xmlSecErrorsSafeString(xmlSecKeyDataStoreGetName(store)), NULL, XMLSEC_ERRORS_R_CERT_VERIFY_FAILED, "cert with subject name %s could not be verified, errcode %d", cert->subjectName, PORT_GetError()); break; } return (NULL); } /** * xmlSecNssX509StoreAdoptCert: * @store: the pointer to X509 key data store klass. * @cert: the pointer to NSS X509 certificate. * @type: the certificate type (trusted/untrusted). * * Adds trusted (root) or untrusted certificate to the store. * * Returns: 0 on success or a negative value if an error occurs. */ int xmlSecNssX509StoreAdoptCert(xmlSecKeyDataStorePtr store, CERTCertificate* cert, xmlSecKeyDataType type ATTRIBUTE_UNUSED) { xmlSecNssX509StoreCtxPtr ctx; int ret; xmlSecAssert2(xmlSecKeyDataStoreCheckId(store, xmlSecNssX509StoreId), -1); xmlSecAssert2(cert != NULL, -1); ctx = xmlSecNssX509StoreGetCtx(store); xmlSecAssert2(ctx != NULL, -1); if(ctx->certsList == NULL) { ctx->certsList = CERT_NewCertList(); if(ctx->certsList == NULL) { xmlSecError(XMLSEC_ERRORS_HERE, xmlSecErrorsSafeString(xmlSecKeyDataStoreGetName(store)), "CERT_NewCertList", XMLSEC_ERRORS_R_CRYPTO_FAILED, "error code=%d", PORT_GetError()); return(-1); } } ret = CERT_AddCertToListTail(ctx->certsList, cert); if(ret != SECSuccess) { xmlSecError(XMLSEC_ERRORS_HERE, xmlSecErrorsSafeString(xmlSecKeyDataStoreGetName(store)), "CERT_AddCertToListTail", XMLSEC_ERRORS_R_CRYPTO_FAILED, "error code=%d", PORT_GetError()); return(-1); } return(0); } static int xmlSecNssX509StoreInitialize(xmlSecKeyDataStorePtr store) { xmlSecNssX509StoreCtxPtr ctx; xmlSecAssert2(xmlSecKeyDataStoreCheckId(store, xmlSecNssX509StoreId), -1); ctx = xmlSecNssX509StoreGetCtx(store); xmlSecAssert2(ctx != NULL, -1); memset(ctx, 0, sizeof(xmlSecNssX509StoreCtx)); return(0); } static void xmlSecNssX509StoreFinalize(xmlSecKeyDataStorePtr store) { xmlSecNssX509StoreCtxPtr ctx; xmlSecAssert(xmlSecKeyDataStoreCheckId(store, xmlSecNssX509StoreId)); ctx = xmlSecNssX509StoreGetCtx(store); xmlSecAssert(ctx != NULL); if (ctx->certsList) { CERT_DestroyCertList(ctx->certsList); ctx->certsList = NULL; } memset(ctx, 0, sizeof(xmlSecNssX509StoreCtx)); } /***************************************************************************** * * Low-level x509 functions * *****************************************************************************/ static CERTCertificate* xmlSecNssX509FindCert(xmlChar *subjectName, xmlChar *issuerName, xmlChar *issuerSerial, xmlChar *ski) { CERTCertificate *cert = NULL; xmlChar *p = NULL; CERTName *name = NULL; SECItem *nameitem = NULL; PRArenaPool *arena = NULL; if (subjectName != NULL) { p = xmlSecNssX509NameRead(subjectName, xmlStrlen(subjectName)); if (p == NULL) { xmlSecError(XMLSEC_ERRORS_HERE, NULL, "xmlSecNssX509NameRead", XMLSEC_ERRORS_R_XMLSEC_FAILED, "subject=%s", xmlSecErrorsSafeString(subjectName)); goto done; } arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); if (arena == NULL) { xmlSecError(XMLSEC_ERRORS_HERE, NULL, "PORT_NewArena", XMLSEC_ERRORS_R_CRYPTO_FAILED, XMLSEC_ERRORS_NO_MESSAGE); goto done; } name = CERT_AsciiToName((char*)p); if (name == NULL) { xmlSecError(XMLSEC_ERRORS_HERE, NULL, "CERT_AsciiToName", XMLSEC_ERRORS_R_XMLSEC_FAILED, XMLSEC_ERRORS_NO_MESSAGE); goto done; } nameitem = SEC_ASN1EncodeItem(arena, NULL, (void *)name, SEC_ASN1_GET(CERT_NameTemplate)); if (nameitem == NULL) { xmlSecError(XMLSEC_ERRORS_HERE, NULL, "SEC_ASN1EncodeItem", XMLSEC_ERRORS_R_XMLSEC_FAILED, "error code=%d", PORT_GetError()); goto done; } cert = CERT_FindCertByName(CERT_GetDefaultCertDB(), nameitem); goto done; } if((issuerName != NULL) && (issuerSerial != NULL)) { CERTIssuerAndSN issuerAndSN; p = xmlSecNssX509NameRead(issuerName, xmlStrlen(issuerName)); if (p == NULL) { xmlSecError(XMLSEC_ERRORS_HERE, NULL, "xmlSecNssX509NameRead", XMLSEC_ERRORS_R_XMLSEC_FAILED, "issuer=%s", xmlSecErrorsSafeString(issuerName)); goto done; } arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); if (arena == NULL) { xmlSecError(XMLSEC_ERRORS_HERE, NULL, "PORT_NewArena", XMLSEC_ERRORS_R_CRYPTO_FAILED, XMLSEC_ERRORS_NO_MESSAGE); goto done; } name = CERT_AsciiToName((char*)p); if (name == NULL) { xmlSecError(XMLSEC_ERRORS_HERE, NULL, "CERT_AsciiToName", XMLSEC_ERRORS_R_XMLSEC_FAILED, XMLSEC_ERRORS_NO_MESSAGE); goto done; } nameitem = SEC_ASN1EncodeItem(arena, NULL, (void *)name, SEC_ASN1_GET(CERT_NameTemplate)); if (nameitem == NULL) { xmlSecError(XMLSEC_ERRORS_HERE, NULL, "SEC_ASN1EncodeItem", XMLSEC_ERRORS_R_XMLSEC_FAILED, "error code=%d", PORT_GetError()); goto done; } memset(&issuerAndSN, 0, sizeof(issuerAndSN)); issuerAndSN.derIssuer.data = nameitem->data; issuerAndSN.derIssuer.len = nameitem->len; /* TBD: serial num can be arbitrarily long */ xmlSecNssNumToItem(&issuerAndSN.serialNumber, PORT_Atoi((char *)issuerSerial)); cert = CERT_FindCertByIssuerAndSN(CERT_GetDefaultCertDB(), &issuerAndSN); SECITEM_FreeItem(&issuerAndSN.serialNumber, PR_FALSE); goto done; } if(ski != NULL) { SECItem subjKeyID; int len; len = xmlSecBase64Decode(ski, (xmlSecByte*)ski, xmlStrlen(ski)); if(len < 0) { xmlSecError(XMLSEC_ERRORS_HERE, NULL, "xmlSecBase64Decode", XMLSEC_ERRORS_R_XMLSEC_FAILED, "ski=%s", xmlSecErrorsSafeString(ski)); goto done; } memset(&subjKeyID, 0, sizeof(subjKeyID)); subjKeyID.data = ski; subjKeyID.len = xmlStrlen(ski); cert = CERT_FindCertBySubjectKeyID(CERT_GetDefaultCertDB(), &subjKeyID); } done: if (p != NULL) { PORT_Free(p); } if (arena != NULL) { PORT_FreeArena(arena, PR_FALSE); } if (name != NULL) { CERT_DestroyName(name); } return(cert); } static xmlSecByte * xmlSecNssX509NameRead(xmlSecByte *str, int len) { xmlSecByte name[256]; xmlSecByte value[256]; xmlSecByte *retval = NULL; xmlSecByte *p = NULL; int nameLen, valueLen; xmlSecAssert2(str != NULL, NULL); /* return string should be no longer than input string */ retval = (xmlSecByte *)PORT_Alloc(len+1); if(retval == NULL) { xmlSecError(XMLSEC_ERRORS_HERE, NULL, "PORT_Alloc", XMLSEC_ERRORS_R_MALLOC_FAILED, XMLSEC_ERRORS_NO_MESSAGE); return(NULL); } p = retval; while(len > 0) { /* skip spaces after comma or semicolon */ while((len > 0) && isspace(*str)) { ++str; --len; } nameLen = xmlSecNssX509NameStringRead(&str, &len, name, sizeof(name), '=', 0); if(nameLen < 0) { xmlSecError(XMLSEC_ERRORS_HERE, NULL, "xmlSecNssX509NameStringRead", XMLSEC_ERRORS_R_XMLSEC_FAILED, XMLSEC_ERRORS_NO_MESSAGE); goto done; } memcpy(p, name, nameLen); p+=nameLen; *p++='='; if(len > 0) { ++str; --len; if((*str) == '\"') { valueLen = xmlSecNssX509NameStringRead(&str, &len, value, sizeof(value), '"', 1); if(valueLen < 0) { xmlSecError(XMLSEC_ERRORS_HERE, NULL, "xmlSecNssX509NameStringRead", XMLSEC_ERRORS_R_XMLSEC_FAILED, XMLSEC_ERRORS_NO_MESSAGE); goto done; } /* skip spaces before comma or semicolon */ while((len > 0) && isspace(*str)) { ++str; --len; } if((len > 0) && ((*str) != ',')) { xmlSecError(XMLSEC_ERRORS_HERE, NULL, NULL, XMLSEC_ERRORS_R_INVALID_DATA, "comma is expected"); goto done; } if(len > 0) { ++str; --len; } *p++='\"'; memcpy(p, value, valueLen); p+=valueLen; *p++='\"'; } else if((*str) == '#') { /* TODO: read octect values */ xmlSecError(XMLSEC_ERRORS_HERE, NULL, NULL, XMLSEC_ERRORS_R_INVALID_DATA, "reading octect values is not implemented yet"); goto done; } else { valueLen = xmlSecNssX509NameStringRead(&str, &len, value, sizeof(value), ',', 1); if(valueLen < 0) { xmlSecError(XMLSEC_ERRORS_HERE, NULL, "xmlSecNssX509NameStringRead", XMLSEC_ERRORS_R_XMLSEC_FAILED, XMLSEC_ERRORS_NO_MESSAGE); goto done; } memcpy(p, value, valueLen); p+=valueLen; if (len > 0) *p++=','; } } else { valueLen = 0; } if(len > 0) { ++str; --len; } } *p = 0; return(retval); done: PORT_Free(retval); return (NULL); } static int xmlSecNssX509NameStringRead(xmlSecByte **str, int *strLen, xmlSecByte *res, int resLen, xmlSecByte delim, int ingoreTrailingSpaces) { xmlSecByte *p, *q, *nonSpace; xmlSecAssert2(str != NULL, -1); xmlSecAssert2(strLen != NULL, -1); xmlSecAssert2(res != NULL, -1); p = (*str); nonSpace = q = res; while(((p - (*str)) < (*strLen)) && ((*p) != delim) && ((q - res) < resLen)) { if((*p) != '\\') { if(ingoreTrailingSpaces && !isspace(*p)) { nonSpace = q; } *(q++) = *(p++); } else { ++p; nonSpace = q; if(xmlSecIsHex((*p))) { if((p - (*str) + 1) >= (*strLen)) { xmlSecError(XMLSEC_ERRORS_HERE, NULL, NULL, XMLSEC_ERRORS_R_INVALID_DATA, "two hex digits expected"); return(-1); } *(q++) = xmlSecGetHex(p[0]) * 16 + xmlSecGetHex(p[1]); p += 2; } else { if(((++p) - (*str)) >= (*strLen)) { xmlSecError(XMLSEC_ERRORS_HERE, NULL, NULL, XMLSEC_ERRORS_R_INVALID_DATA, "escaped symbol missed"); return(-1); } *(q++) = *(p++); } } } if(((p - (*str)) < (*strLen)) && ((*p) != delim)) { xmlSecError(XMLSEC_ERRORS_HERE, NULL, NULL, XMLSEC_ERRORS_R_INVALID_SIZE, "buffer is too small"); return(-1); } (*strLen) -= (p - (*str)); (*str) = p; return((ingoreTrailingSpaces) ? nonSpace - res + 1 : q - res); } /* code lifted from NSS */ static void xmlSecNssNumToItem(SECItem *it, unsigned long ui) { unsigned char bb[5]; int len; bb[0] = 0; bb[1] = (unsigned char) (ui >> 24); bb[2] = (unsigned char) (ui >> 16); bb[3] = (unsigned char) (ui >> 8); bb[4] = (unsigned char) (ui); /* ** Small integers are encoded in a single byte. Larger integers ** require progressively more space. */ if (ui > 0x7f) { if (ui > 0x7fff) { if (ui > 0x7fffffL) { if (ui >= 0x80000000L) { len = 5; } else { len = 4; } } else { len = 3; } } else { len = 2; } } else { len = 1; } it->data = (unsigned char *)PORT_Alloc(len); if (it->data == NULL) { return; } it->len = len; PORT_Memcpy(it->data, bb + (sizeof(bb) - len), len); } #endif /* XMLSEC_NO_X509 */