NS_IMETHODIMP DigestOutputStream::Write(const char* aBuf, uint32_t aCount, uint32_t* retval) { nsNSSShutDownPreventionLock lock; if (isAlreadyShutDown()) { return NS_ERROR_NOT_AVAILABLE; } nsresult rv = MapSECStatus(PK11_DigestOp(mDigestContext, uint8_t_ptr_cast(aBuf), aCount)); NS_ENSURE_SUCCESS(rv, rv); return mOutputStream->Write(aBuf, aCount, retval); }
// Called on the worker thread. nsresult BackgroundFileSaver::ProcessStateChange() { nsresult rv; // We might have been notified because the operation is complete, verify. if (CheckCompletion()) { return NS_OK; } // Get a copy of the current shared state for the worker thread. nsCOMPtr<nsIFile> initialTarget; bool initialTargetKeepPartial; nsCOMPtr<nsIFile> renamedTarget; bool renamedTargetKeepPartial; bool sha256Enabled; bool append; { MutexAutoLock lock(mLock); initialTarget = mInitialTarget; initialTargetKeepPartial = mInitialTargetKeepPartial; renamedTarget = mRenamedTarget; renamedTargetKeepPartial = mRenamedTargetKeepPartial; sha256Enabled = mSha256Enabled; append = mAppend; // From now on, another attention event needs to be posted if state changes. mWorkerThreadAttentionRequested = false; } // The initial target can only be null if it has never been assigned. In this // case, there is nothing to do since we never created any output file. if (!initialTarget) { return NS_OK; } // Determine if we are processing the attention request for the first time. bool isContinuation = !!mActualTarget; if (!isContinuation) { // Assign the target file for the first time. mActualTarget = initialTarget; mActualTargetKeepPartial = initialTargetKeepPartial; } // Verify whether we have actually been instructed to use a different file. // This may happen the first time this function is executed, if SetTarget was // called two times before the worker thread processed the attention request. bool equalToCurrent = false; if (renamedTarget) { rv = mActualTarget->Equals(renamedTarget, &equalToCurrent); NS_ENSURE_SUCCESS(rv, rv); if (!equalToCurrent) { // If we were asked to rename the file but the initial file did not exist, // we simply create the file in the renamed location. We avoid this check // if we have already started writing the output file ourselves. bool exists = true; if (!isContinuation) { rv = mActualTarget->Exists(&exists); NS_ENSURE_SUCCESS(rv, rv); } if (exists) { // We are moving the previous target file to a different location. nsCOMPtr<nsIFile> renamedTargetParentDir; rv = renamedTarget->GetParent(getter_AddRefs(renamedTargetParentDir)); NS_ENSURE_SUCCESS(rv, rv); nsAutoString renamedTargetName; rv = renamedTarget->GetLeafName(renamedTargetName); NS_ENSURE_SUCCESS(rv, rv); // We must delete any existing target file before moving the current // one. rv = renamedTarget->Exists(&exists); NS_ENSURE_SUCCESS(rv, rv); if (exists) { rv = renamedTarget->Remove(false); NS_ENSURE_SUCCESS(rv, rv); } // Move the file. If this fails, we still reference the original file // in mActualTarget, so that it is deleted if requested. If this // succeeds, the nsIFile instance referenced by mActualTarget mutates // and starts pointing to the new file, but we'll discard the reference. rv = mActualTarget->MoveTo(renamedTargetParentDir, renamedTargetName); NS_ENSURE_SUCCESS(rv, rv); } // Now we can update the actual target file name. mActualTarget = renamedTarget; mActualTargetKeepPartial = renamedTargetKeepPartial; } } // Notify if the target file name actually changed. if (!equalToCurrent) { // We must clone the nsIFile instance because mActualTarget is not // immutable, it may change if the target is renamed later. nsCOMPtr<nsIFile> actualTargetToNotify; rv = mActualTarget->Clone(getter_AddRefs(actualTargetToNotify)); NS_ENSURE_SUCCESS(rv, rv); RefPtr<NotifyTargetChangeRunnable> event = new NotifyTargetChangeRunnable(this, actualTargetToNotify); NS_ENSURE_TRUE(event, NS_ERROR_FAILURE); rv = mControlThread->Dispatch(event, NS_DISPATCH_NORMAL); NS_ENSURE_SUCCESS(rv, rv); } if (isContinuation) { // The pending rename operation might be the last task before finishing. We // may return here only if we have already created the target file. if (CheckCompletion()) { return NS_OK; } // Even if the operation did not complete, the pipe input stream may be // empty and may have been closed already. We detect this case using the // Available property, because it never returns an error if there is more // data to be consumed. If the pipe input stream is closed, we just exit // and wait for more calls like SetTarget or Finish to be invoked on the // control thread. However, we still truncate the file or create the // initial digest context if we are expected to do that. uint64_t available; rv = mPipeInputStream->Available(&available); if (NS_FAILED(rv)) { return NS_OK; } } // Create the digest context if requested and NSS hasn't been shut down. if (sha256Enabled && !mDigestContext) { nsNSSShutDownPreventionLock lock; if (!isAlreadyShutDown()) { mDigestContext = UniquePK11Context( PK11_CreateDigestContext(SEC_OID_SHA256)); NS_ENSURE_TRUE(mDigestContext, NS_ERROR_OUT_OF_MEMORY); } } // When we are requested to append to an existing file, we should read the // existing data and ensure we include it as part of the final hash. if (mDigestContext && append && !isContinuation) { nsCOMPtr<nsIInputStream> inputStream; rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), mActualTarget, PR_RDONLY | nsIFile::OS_READAHEAD); if (rv != NS_ERROR_FILE_NOT_FOUND) { NS_ENSURE_SUCCESS(rv, rv); char buffer[BUFFERED_IO_SIZE]; while (true) { uint32_t count; rv = inputStream->Read(buffer, BUFFERED_IO_SIZE, &count); NS_ENSURE_SUCCESS(rv, rv); if (count == 0) { // We reached the end of the file. break; } nsNSSShutDownPreventionLock lock; if (isAlreadyShutDown()) { return NS_ERROR_NOT_AVAILABLE; } nsresult rv = MapSECStatus(PK11_DigestOp(mDigestContext.get(), uint8_t_ptr_cast(buffer), count)); NS_ENSURE_SUCCESS(rv, rv); } rv = inputStream->Close(); NS_ENSURE_SUCCESS(rv, rv); } } // We will append to the initial target file only if it was requested by the // caller, but we'll always append on subsequent accesses to the target file. int32_t creationIoFlags; if (isContinuation) { creationIoFlags = PR_APPEND; } else { creationIoFlags = (append ? PR_APPEND : PR_TRUNCATE) | PR_CREATE_FILE; } // Create the target file, or append to it if we already started writing it. // The 0600 permissions are used while the file is being downloaded, and for // interrupted downloads. Those may be located in the system temporary // directory, as well as the target directory, and generally have a ".part" // extension. Those part files should never be group or world-writable even // if the umask allows it. nsCOMPtr<nsIOutputStream> outputStream; rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), mActualTarget, PR_WRONLY | creationIoFlags, 0600); NS_ENSURE_SUCCESS(rv, rv); outputStream = NS_BufferOutputStream(outputStream, BUFFERED_IO_SIZE); if (!outputStream) { return NS_ERROR_FAILURE; } // Wrap the output stream so that it feeds the digest context if needed. if (mDigestContext) { // No need to acquire the NSS lock here, DigestOutputStream must acquire it // in any case before each asynchronous write. Constructing the // DigestOutputStream cannot fail. Passing mDigestContext to // DigestOutputStream is safe, because BackgroundFileSaver always outlives // the outputStream. BackgroundFileSaver is reference-counted before the // call to AsyncCopy, and mDigestContext is never destroyed before // AsyncCopyCallback. outputStream = new DigestOutputStream(outputStream, mDigestContext.get()); } // Start copying our input to the target file. No errors can be raised past // this point if the copy starts, since they should be handled by the thread. { MutexAutoLock lock(mLock); rv = NS_AsyncCopy(mPipeInputStream, outputStream, mWorkerThread, NS_ASYNCCOPY_VIA_READSEGMENTS, 4096, AsyncCopyCallback, this, false, true, getter_AddRefs(mAsyncCopyContext), GetProgressCallback()); if (NS_FAILED(rv)) { NS_WARNING("NS_AsyncCopy failed."); mAsyncCopyContext = nullptr; return rv; } } // If the operation succeeded, we must ensure that we keep this object alive // for the entire duration of the copy, since only the raw pointer will be // provided as the argument of the AsyncCopyCallback function. We can add the // reference now, after NS_AsyncCopy returned, because it always starts // processing asynchronously, and there is no risk that the callback is // invoked before we reach this point. If the operation failed instead, then // AsyncCopyCallback will never be called. NS_ADDREF_THIS(); return NS_OK; }
SECStatus CertVerifier::VerifySSLServerCert(const UniqueCERTCertificate& peerCert, /*optional*/ const SECItem* stapledOCSPResponse, Time time, /*optional*/ void* pinarg, const char* hostname, /*out*/ ScopedCERTCertList& builtChain, /*optional*/ bool saveIntermediatesInPermanentDatabase, /*optional*/ Flags flags, /*optional out*/ SECOidTag* evOidPolicy, /*optional out*/ OCSPStaplingStatus* ocspStaplingStatus, /*optional out*/ KeySizeStatus* keySizeStatus, /*optional out*/ SHA1ModeResult* sha1ModeResult, /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo) { PR_ASSERT(peerCert); // XXX: PR_ASSERT(pinarg) PR_ASSERT(hostname); PR_ASSERT(hostname[0]); if (evOidPolicy) { *evOidPolicy = SEC_OID_UNKNOWN; } if (!hostname || !hostname[0]) { PR_SetError(SSL_ERROR_BAD_CERT_DOMAIN, 0); return SECFailure; } // CreateCertErrorRunnable assumes that CheckCertHostname is only called // if VerifyCert succeeded. SECStatus rv = VerifyCert(peerCert.get(), certificateUsageSSLServer, time, pinarg, hostname, builtChain, flags, stapledOCSPResponse, evOidPolicy, ocspStaplingStatus, keySizeStatus, sha1ModeResult, pinningTelemetryInfo); if (rv != SECSuccess) { return rv; } Input peerCertInput; Result result = peerCertInput.Init(peerCert->derCert.data, peerCert->derCert.len); if (result != Success) { PR_SetError(MapResultToPRErrorCode(result), 0); return SECFailure; } Input stapledOCSPResponseInput; Input* responseInputPtr = nullptr; if (stapledOCSPResponse) { result = stapledOCSPResponseInput.Init(stapledOCSPResponse->data, stapledOCSPResponse->len); if (result != Success) { // The stapled OCSP response was too big. PR_SetError(SEC_ERROR_OCSP_MALFORMED_RESPONSE, 0); return SECFailure; } responseInputPtr = &stapledOCSPResponseInput; } if (!(flags & FLAG_TLS_IGNORE_STATUS_REQUEST)) { result = CheckTLSFeaturesAreSatisfied(peerCertInput, responseInputPtr); if (result != Success) { PR_SetError(MapResultToPRErrorCode(result), 0); return SECFailure; } } Input hostnameInput; result = hostnameInput.Init(uint8_t_ptr_cast(hostname), strlen(hostname)); if (result != Success) { PR_SetError(SEC_ERROR_INVALID_ARGS, 0); return SECFailure; } bool isBuiltInRoot; result = IsCertChainRootBuiltInRoot(builtChain, isBuiltInRoot); if (result != Success) { PR_SetError(MapResultToPRErrorCode(result), 0); return SECFailure; } BRNameMatchingPolicy nameMatchingPolicy( isBuiltInRoot ? mNameMatchingMode : BRNameMatchingPolicy::Mode::DoNotEnforce); result = CheckCertHostname(peerCertInput, hostnameInput, nameMatchingPolicy); if (result != Success) { // Treat malformed name information as a domain mismatch. if (result == Result::ERROR_BAD_DER) { PR_SetError(SSL_ERROR_BAD_CERT_DOMAIN, 0); } else { PR_SetError(MapResultToPRErrorCode(result), 0); } return SECFailure; } if (saveIntermediatesInPermanentDatabase) { SaveIntermediateCerts(builtChain); } return SECSuccess; }
SECStatus CertVerifier::VerifySSLServerCert(CERTCertificate* peerCert, /*optional*/ const SECItem* stapledOCSPResponse, Time time, /*optional*/ void* pinarg, const char* hostname, bool saveIntermediatesInPermanentDatabase, Flags flags, /*optional out*/ ScopedCERTCertList* builtChain, /*optional out*/ SECOidTag* evOidPolicy, /*optional out*/ OCSPStaplingStatus* ocspStaplingStatus, /*optional out*/ KeySizeStatus* keySizeStatus) { PR_ASSERT(peerCert); // XXX: PR_ASSERT(pinarg) PR_ASSERT(hostname); PR_ASSERT(hostname[0]); if (builtChain) { *builtChain = nullptr; } if (evOidPolicy) { *evOidPolicy = SEC_OID_UNKNOWN; } if (!hostname || !hostname[0]) { PR_SetError(SSL_ERROR_BAD_CERT_DOMAIN, 0); return SECFailure; } ScopedCERTCertList builtChainTemp; // CreateCertErrorRunnable assumes that CheckCertHostname is only called // if VerifyCert succeeded. SECStatus rv = VerifyCert(peerCert, certificateUsageSSLServer, time, pinarg, hostname, flags, stapledOCSPResponse, &builtChainTemp, evOidPolicy, ocspStaplingStatus, keySizeStatus); if (rv != SECSuccess) { return rv; } Input peerCertInput; Result result = peerCertInput.Init(peerCert->derCert.data, peerCert->derCert.len); if (result != Success) { PR_SetError(MapResultToPRErrorCode(result), 0); return SECFailure; } Input hostnameInput; result = hostnameInput.Init(uint8_t_ptr_cast(hostname), strlen(hostname)); if (result != Success) { PR_SetError(SEC_ERROR_INVALID_ARGS, 0); return SECFailure; } result = CheckCertHostname(peerCertInput, hostnameInput); if (result != Success) { PR_SetError(MapResultToPRErrorCode(result), 0); return SECFailure; } if (saveIntermediatesInPermanentDatabase) { SaveIntermediateCerts(builtChainTemp); } if (builtChain) { *builtChain = builtChainTemp.forget(); } return SECSuccess; }