/* * Copyright (c) 2014 Samsung Electronics Co. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License * * * @file ocsp.cpp * @author Dongsun Lee (ds73.lee@samsung.com) * @version 1.0 * @brief OCSP implementation. */ #include #include #include #include #include #include #include #include #include #include #include #include #include /* Maximum leeway in validity period: default 5 minutes */ #define MAX_VALIDITY_PERIOD (5 * 60) /* Timeout in seconds for ocsp response */ #define OCSP_TIMEOUT 30 namespace CKM { namespace { typedef std::unique_ptr> BioUniquePtr; void BIO_write_and_free(BIO *bio) { if (!bio) return; std::vector message(1024); int size = BIO_read(bio, message.data(), message.size()); if (size > 0) { message.resize(size); LogError("OCSP error description:" << std::string(message.begin(), message.end())); } BIO_free_all(bio); } } // namespace anonymous OCSPModule::OCSPModule() { // Do nothing. } OCSPModule::~OCSPModule() { // Do nothing. } int OCSPModule::verify(const CertificateImplVector &certificateChain) { bool unsupported = false; // ocsp is unsupported in certificate in chain (except root CA) // create trusted store X509_STACK_PTR trustedCerts = create_x509_stack(); // skip first 2 certificates for (auto it = certificateChain.cbegin() + 2; it < certificateChain.cend(); it++) { if (it->empty()) { LogError("Error. Broken certificate chain."); return CKM_API_OCSP_STATUS_INTERNAL_ERROR; } sk_X509_push(trustedCerts.get(), it->getX509()); } for (int i = 0; i < static_cast(certificateChain.size()) - 1; i++) {// except root certificate if (certificateChain[i].empty() || certificateChain[i + 1].empty()) { LogError("Error. Broken certificate chain."); return CKM_API_OCSP_STATUS_INTERNAL_ERROR; } X509 *cert = certificateChain[i].getX509(); X509 *issuer = certificateChain[i + 1].getX509(); std::string url = certificateChain[i].getOCSPURL(); if (url.empty()) { LogError("Certificate in certchain[" << i << "] does not provide OCSP extension."); unsupported = true; continue; } int result = ocsp_verify(cert, issuer, trustedCerts.get(), url); // remove first element from trustedCerts store sk_X509_delete(trustedCerts.get(), 0); if (result != CKM_API_OCSP_STATUS_GOOD) { LogError("Fail to OCSP certification check. Errorcode=[" << result << "], on certChain[" << i << "]"); return result; } } if (unsupported) return CKM_API_OCSP_STATUS_UNSUPPORTED; return CKM_API_OCSP_STATUS_GOOD; } int OCSPModule::ocsp_verify(X509 *cert, X509 *issuer, STACK_OF(X509) *trustedCerts, const std::string &constUrl) { OCSP_REQUEST *req = NULL; OCSP_RESPONSE *resp = NULL; OCSP_BASICRESP *bs = NULL; OCSP_CERTID *certid = NULL; BIO *cbio = NULL; SSL_CTX *use_ssl_ctx = NULL; std::string host, port, path; ASN1_GENERALIZEDTIME *rev = NULL; ASN1_GENERALIZEDTIME *thisupd = NULL; ASN1_GENERALIZEDTIME *nextupd = NULL; int use_ssl = 0; int ocspStatus = -1; int i = 0; long nsec = MAX_VALIDITY_PERIOD, maxage = -1; char subj_buf[256]; int reason = 0; // const char *reason_str = NULL;0 X509_STORE *trustedStore = NULL; BioUniquePtr bioLogger(BIO_new(BIO_s_mem()), BIO_write_and_free); std::vector url(constUrl.begin(), constUrl.end()); url.push_back(0); std::string headerHost; { char *chost = NULL, *cport = NULL, *cpath = NULL; if (!OCSP_parse_url(url.data(), &chost, &cport, &cpath, &use_ssl)) /* report error */ return CKM_API_OCSP_STATUS_INVALID_URL; if (chost) { host = chost; headerHost = chost; } if (cport) port = cport; if (cpath) path = cpath; OPENSSL_free(chost); OPENSSL_free(cport); OPENSSL_free(cpath); } LogDebug("Host: " << host); LogDebug("Port: " << port); LogDebug("Path: " << path); LogDebug("Use_ssl: " << use_ssl); std::unique_ptr proxy(vconf_get_str(VCONFKEY_NETWORK_PROXY), free); if (proxy && strlen(proxy.get()) > 0) { char *phost = NULL, *pport = NULL, *ppath = NULL; LogDebug("Using proxy: " << proxy.get()); if (!OCSP_parse_url(proxy.get(), &phost, &pport, &ppath, &use_ssl)) { return CKM_API_OCSP_STATUS_INVALID_URL; } path = url.data(); if (phost) host = phost; if (pport) port = pport; OPENSSL_free(phost); OPENSSL_free(pport); OPENSSL_free(ppath); } cbio = BIO_new_connect(host.c_str()); if (cbio == NULL) { /*BIO_printf(bio_err, "Error creating connect BIO\n");*/ /* report error */ LogError("Connection to ocsp host failed: " << host); return CKM_API_OCSP_STATUS_INTERNAL_ERROR; } if (!port.empty()) BIO_set_conn_port(cbio, port.c_str()); if (use_ssl == 1) { BIO *sbio = NULL; use_ssl_ctx = SSL_CTX_new(SSLv23_client_method()); if (use_ssl_ctx == NULL) { /* report error */ return CKM_API_OCSP_STATUS_INTERNAL_ERROR; } SSL_CTX_set_mode(use_ssl_ctx, SSL_MODE_AUTO_RETRY); sbio = BIO_new_ssl(use_ssl_ctx, 1); if (sbio == NULL) { /* report error */ return CKM_API_OCSP_STATUS_INTERNAL_ERROR; } cbio = BIO_push(sbio, cbio); if (cbio == NULL) { /* report error */ return CKM_API_OCSP_STATUS_INTERNAL_ERROR; } } if (BIO_do_connect(cbio) <= 0) { LogDebug("Error in BIO_do_connect."); ERR_print_errors(bioLogger.get()); /* report error */ if (use_ssl && use_ssl_ctx) SSL_CTX_free(use_ssl_ctx); use_ssl_ctx = NULL; if (cbio != NULL) BIO_free_all(cbio); cbio = NULL; return CKM_API_OCSP_STATUS_NET_ERROR; } req = OCSP_REQUEST_new(); if (req == NULL) { LogDebug("Error in OCPS_REQUEST_new"); return CKM_API_OCSP_STATUS_INTERNAL_ERROR; } certid = OCSP_cert_to_id(NULL, cert, issuer); if (certid == NULL) { LogDebug("Error in OCSP_cert_to_id"); return CKM_API_OCSP_STATUS_INTERNAL_ERROR; } if (OCSP_request_add0_id(req, certid) == NULL) { LogDebug("Error in OCSP_request_add0_id"); return CKM_API_OCSP_STATUS_INTERNAL_ERROR; } std::unique_ptr ctx(OCSP_sendreq_new(cbio, path.c_str(), NULL, -1), OCSP_REQ_CTX_free); if (!ctx) { LogError("Error creating OCSP_REQ_CTX"); return CKM_API_OCSP_STATUS_INTERNAL_ERROR; } if (!OCSP_REQ_CTX_add1_header(ctx.get(), "host", headerHost.c_str())) { LogError("Error adding header"); return CKM_API_OCSP_STATUS_INTERNAL_ERROR; } if (!OCSP_REQ_CTX_set1_req(ctx.get(), req)) { LogError("Error setting ocsp request"); return CKM_API_OCSP_STATUS_INTERNAL_ERROR; } int fd; if (BIO_get_fd(cbio, &fd) < 0) { LogError("Error extracting fd from bio"); return CKM_API_OCSP_STATUS_INTERNAL_ERROR; } for (;;) { fd_set confds; int req_timeout = OCSP_TIMEOUT; struct timeval tv; int rv = OCSP_sendreq_nbio(&resp, ctx.get()); if (rv != -1) break; FD_ZERO(&confds); FD_SET(fd, &confds); tv.tv_usec = 0; tv.tv_sec = req_timeout; if (BIO_should_read(cbio)) { rv = select(fd + 1, &confds, NULL, NULL, &tv); } else if (BIO_should_write(cbio)) { rv = select(fd + 1, NULL, &confds, NULL, &tv); } else { LogError("Unexpected retry condition\n"); return CKM_API_OCSP_STATUS_INTERNAL_ERROR; } if (rv == 0) { LogError("Timeout on request\n"); break; } if (rv == -1) { LogError("Select error\n"); break; } } if (use_ssl && use_ssl_ctx) SSL_CTX_free(use_ssl_ctx); use_ssl_ctx = NULL; if (cbio != NULL) BIO_free_all(cbio); cbio = NULL; if (!resp) { /*BIO_printf(bio_err, "Error querying OCSP responsder\n");*/ /* report error */ /* free stuff */ OCSP_REQUEST_free(req); return CKM_API_OCSP_STATUS_NET_ERROR; } i = OCSP_response_status(resp); if (i != OCSP_RESPONSE_STATUS_SUCCESSFUL) { /* report error */ ERR_print_errors(bioLogger.get()); /* free stuff */ OCSP_REQUEST_free(req); OCSP_RESPONSE_free(resp); return CKM_API_OCSP_STATUS_REMOTE_ERROR; } bs = OCSP_response_get1_basic(resp); if (!bs) { /* report error */ ERR_print_errors(bioLogger.get()); /* free stuff */ OCSP_REQUEST_free(req); OCSP_RESPONSE_free(resp); LogDebug("Error in OCSP_response_get1_basic"); return CKM_API_OCSP_STATUS_INVALID_RESPONSE; } if (trustedCerts != NULL) { trustedStore = X509_STORE_new(); for (int tmpIdx = 0; tmpIdx < sk_X509_num(trustedCerts); tmpIdx++) X509_STORE_add_cert(trustedStore, sk_X509_value(trustedCerts, tmpIdx)); X509_STORE_add_cert(trustedStore, issuer); } // Additional certificates to search for signer. // OCSP response may not contain issuer certificate in this case // we must pass it by 'other' certificates. X509_STACK_PTR verifyOther = create_x509_stack(); sk_X509_push(verifyOther.get(), issuer); int response = OCSP_basic_verify(bs, verifyOther.get(), trustedStore, 0); verifyOther.reset(); if (response <= 0) { OCSP_REQUEST_free(req); OCSP_RESPONSE_free(resp); OCSP_BASICRESP_free(bs); X509_STORE_free(trustedStore); ERR_print_errors(bioLogger.get()); return CKM_API_OCSP_STATUS_INVALID_RESPONSE; } if ((i = OCSP_check_nonce(req, bs)) <= 0) { if (i == -1) { ERR_print_errors(bioLogger.get()); } else { /* report error */ ERR_print_errors(bioLogger.get()); /* free stuff */ OCSP_REQUEST_free(req); OCSP_RESPONSE_free(resp); OCSP_BASICRESP_free(bs); X509_STORE_free(trustedStore); LogDebug("Error in OCSP_check_nonce"); return CKM_API_OCSP_STATUS_INVALID_RESPONSE; } } (void)X509_NAME_oneline(X509_get_subject_name(cert), subj_buf, 255); if (!OCSP_resp_find_status(bs, certid, &ocspStatus, &reason, &rev, &thisupd, &nextupd)) { /* report error */ ERR_print_errors(bioLogger.get()); /* free stuff */ OCSP_RESPONSE_free(resp); OCSP_REQUEST_free(req); OCSP_BASICRESP_free(bs); X509_STORE_free(trustedStore); LogDebug("Error in OCSP_resp_find_status"); return CKM_API_OCSP_STATUS_INVALID_RESPONSE; } /* Check validity: if invalid write to output BIO so we * know which response this refers to. */ if (!OCSP_check_validity(thisupd, nextupd, nsec, maxage)) { /* report error */ ERR_print_errors(bioLogger.get()); /* free stuff */ OCSP_REQUEST_free(req); OCSP_RESPONSE_free(resp); OCSP_BASICRESP_free(bs); X509_STORE_free(trustedStore); LogDebug("Error in OCSP_check_validity"); return CKM_API_OCSP_STATUS_INVALID_RESPONSE; } if (req != NULL) { OCSP_REQUEST_free(req); req = NULL; } if (resp != NULL) { OCSP_RESPONSE_free(resp); resp = NULL; } if (bs != NULL) { OCSP_BASICRESP_free(bs); bs = NULL; } if (trustedStore != NULL) { X509_STORE_free(trustedStore); trustedStore = NULL; } switch (ocspStatus) { case V_OCSP_CERTSTATUS_GOOD: return CKM_API_OCSP_STATUS_GOOD; case V_OCSP_CERTSTATUS_REVOKED: return CKM_API_OCSP_STATUS_REVOKED; case V_OCSP_CERTSTATUS_UNKNOWN: return CKM_API_OCSP_STATUS_UNKNOWN; default: LogError("Internal openssl error: Certificate status have value is out of bound."); return CKM_API_OCSP_STATUS_INTERNAL_ERROR; } } } // namespace CKM