/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* 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 "OCSPRequestor.h" #include #include "mozilla/Base64.h" #include "mozilla/Scoped.h" #include "nsIURLParser.h" #include "nsNSSCallbacks.h" #include "nsNetCID.h" #include "nsServiceManagerUtils.h" #include "secerr.h" extern PRLogModuleInfo* gCertVerifierLog; namespace mozilla { void ReleaseHttpServerSession(nsNSSHttpServerSession* httpServerSession) { delete httpServerSession; } void ReleaseHttpRequestSession(nsNSSHttpRequestSession* httpRequestSession) { httpRequestSession->Release(); } MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedHTTPServerSession, nsNSSHttpServerSession, ReleaseHttpServerSession) MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedHTTPRequestSession, nsNSSHttpRequestSession, ReleaseHttpRequestSession) } // namespace mozilla namespace mozilla { namespace psm { static nsresult AppendEscapedBase64Item(const SECItem* encodedRequest, nsACString& path) { nsresult rv; nsDependentCSubstring requestAsSubstring( reinterpret_cast(encodedRequest->data), encodedRequest->len); nsCString base64Request; rv = Base64Encode(requestAsSubstring, base64Request); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } MOZ_LOG(gCertVerifierLog, LogLevel::Debug, ("Setting up OCSP GET path, pre path =%s\n", PromiseFlatCString(path).get())); // The path transformation is not a direct url encoding. Three characters // need change '+' -> "%2B", '/' -> "%2F", and '=' -> '%3D'. // http://tools.ietf.org/html/rfc5019#section-5 base64Request.ReplaceSubstring("+", "%2B"); base64Request.ReplaceSubstring("/", "%2F"); base64Request.ReplaceSubstring("=", "%3D"); path.Append(base64Request); return NS_OK; } SECItem* DoOCSPRequest(PLArenaPool* arena, const char* url, const SECItem* encodedRequest, PRIntervalTime timeout, bool useGET) { if (!arena || !url || !encodedRequest || !encodedRequest->data) { PR_SetError(SEC_ERROR_INVALID_ARGS, 0); return nullptr; } uint32_t urlLen = PL_strlen(url); if (urlLen > static_cast(std::numeric_limits::max())) { PR_SetError(SEC_ERROR_INVALID_ARGS, 0); return nullptr; } nsCOMPtr urlParser = do_GetService(NS_STDURLPARSER_CONTRACTID); if (!urlParser) { PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0); return nullptr; } uint32_t schemePos; int32_t schemeLen; uint32_t authorityPos; int32_t authorityLen; uint32_t pathPos; int32_t pathLen; nsresult rv = urlParser->ParseURL(url, static_cast(urlLen), &schemePos, &schemeLen, &authorityPos, &authorityLen, &pathPos, &pathLen); if (NS_FAILED(rv)) { PR_SetError(SEC_ERROR_CERT_BAD_ACCESS_LOCATION, 0); return nullptr; } if (schemeLen < 0 || authorityLen < 0) { PR_SetError(SEC_ERROR_CERT_BAD_ACCESS_LOCATION, 0); return nullptr; } nsAutoCString scheme(url + schemePos, static_cast(schemeLen)); if (!scheme.LowerCaseEqualsLiteral("http")) { // We dont support https:// to avoid loops see Bug 92923 PR_SetError(SEC_ERROR_CERT_BAD_ACCESS_LOCATION, 0); return nullptr; } uint32_t hostnamePos; int32_t hostnameLen; int32_t port; // We do not support urls with user@pass sections in the URL, // In cas we find them we will ignore and try to connect with rv = urlParser->ParseAuthority(url + authorityPos, authorityLen, nullptr, nullptr, nullptr, nullptr, &hostnamePos, &hostnameLen, &port); if (NS_FAILED(rv)) { PR_SetError(SEC_ERROR_CERT_BAD_ACCESS_LOCATION, 0); return nullptr; } if (hostnameLen < 0) { PR_SetError(SEC_ERROR_CERT_BAD_ACCESS_LOCATION, 0); return nullptr; } if (port == -1) { port = 80; } else if (port < 0 || port > 0xffff) { PR_SetError(SEC_ERROR_CERT_BAD_ACCESS_LOCATION, 0); return nullptr; } nsAutoCString hostname(url + authorityPos + hostnamePos, static_cast(hostnameLen)); SEC_HTTP_SERVER_SESSION serverSessionPtr = nullptr; if (nsNSSHttpInterface::createSessionFcn(hostname.BeginReading(), static_cast(port), &serverSessionPtr) != SECSuccess) { PR_SetError(SEC_ERROR_NO_MEMORY, 0); return nullptr; } ScopedHTTPServerSession serverSession( reinterpret_cast(serverSessionPtr)); nsAutoCString path; if (pathLen > 0) { path.Assign(url + pathPos, static_cast(pathLen)); } else { path.Assign("/"); } MOZ_LOG(gCertVerifierLog, LogLevel::Debug, ("Setting up OCSP request: pre all path =%s pathlen=%d\n", path.get(), pathLen)); nsAutoCString method("POST"); if (useGET) { method.Assign("GET"); if (!StringEndsWith(path, NS_LITERAL_CSTRING("/"))) { path.Append("/"); } nsresult rv = AppendEscapedBase64Item(encodedRequest, path); if (NS_WARN_IF(NS_FAILED(rv))) { return nullptr; } } SEC_HTTP_REQUEST_SESSION requestSessionPtr; if (nsNSSHttpInterface::createFcn(serverSession.get(), "http", path.get(), method.get(), timeout, &requestSessionPtr) != SECSuccess) { PR_SetError(SEC_ERROR_NO_MEMORY, 0); return nullptr; } ScopedHTTPRequestSession requestSession( reinterpret_cast(requestSessionPtr)); if (!useGET) { if (nsNSSHttpInterface::setPostDataFcn(requestSession.get(), reinterpret_cast(encodedRequest->data), encodedRequest->len, "application/ocsp-request") != SECSuccess) { PR_SetError(SEC_ERROR_NO_MEMORY, 0); return nullptr; } } uint16_t httpResponseCode; const char* httpResponseData; uint32_t httpResponseDataLen = 0; // 0 means any response size is acceptable if (nsNSSHttpInterface::trySendAndReceiveFcn(requestSession.get(), nullptr, &httpResponseCode, nullptr, nullptr, &httpResponseData, &httpResponseDataLen) != SECSuccess) { PR_SetError(SEC_ERROR_OCSP_SERVER_ERROR, 0); return nullptr; } if (httpResponseCode != 200) { PR_SetError(SEC_ERROR_OCSP_SERVER_ERROR, 0); return nullptr; } SECItem* encodedResponse = SECITEM_AllocItem(arena, nullptr, httpResponseDataLen); if (!encodedResponse) { PR_SetError(SEC_ERROR_NO_MEMORY, 0); return nullptr; } memcpy(encodedResponse->data, httpResponseData, httpResponseDataLen); return encodedResponse; } } } // namespace mozilla::psm