diff options
-rw-r--r-- | android_webview/browser/aw_ssl_host_state_delegate.cc | 11 | ||||
-rw-r--r-- | android_webview/browser/aw_ssl_host_state_delegate.h | 14 | ||||
-rw-r--r-- | chrome/browser/ssl/chrome_ssl_host_state_delegate.h | 4 | ||||
-rw-r--r-- | chrome/browser/ssl/ssl_browser_tests.cc | 160 | ||||
-rw-r--r-- | content/browser/ssl/ssl_policy.cc | 25 | ||||
-rw-r--r-- | content/browser/ssl/ssl_policy_backend.cc | 14 | ||||
-rw-r--r-- | content/browser/ssl/ssl_policy_backend.h | 7 | ||||
-rw-r--r-- | content/public/browser/ssl_host_state_delegate.h | 10 | ||||
-rw-r--r-- | net/test/spawned_test_server/base_test_server.cc | 36 | ||||
-rw-r--r-- | net/test/spawned_test_server/base_test_server.h | 8 | ||||
-rw-r--r-- | tools/metrics/histograms/histograms.xml | 13 |
11 files changed, 262 insertions, 40 deletions
diff --git a/android_webview/browser/aw_ssl_host_state_delegate.cc b/android_webview/browser/aw_ssl_host_state_delegate.cc index 59d717f..1da4747 100644 --- a/android_webview/browser/aw_ssl_host_state_delegate.cc +++ b/android_webview/browser/aw_ssl_host_state_delegate.cc @@ -86,4 +86,15 @@ SSLHostStateDelegate::CertJudgment AwSSLHostStateDelegate::QueryPolicy( : SSLHostStateDelegate::DENIED; } +void AwSSLHostStateDelegate::RevokeUserAllowExceptions( + const std::string& host) { + cert_policy_for_host_.erase(host); +} + +bool AwSSLHostStateDelegate::HasAllowException(const std::string& host) const { + auto policy_iterator = cert_policy_for_host_.find(host); + return policy_iterator != cert_policy_for_host_.end() && + policy_iterator->second.HasAllowException(); +} + } // namespace android_webview diff --git a/android_webview/browser/aw_ssl_host_state_delegate.h b/android_webview/browser/aw_ssl_host_state_delegate.h index bc3e1a8..b5a34f4 100644 --- a/android_webview/browser/aw_ssl_host_state_delegate.h +++ b/android_webview/browser/aw_ssl_host_state_delegate.h @@ -30,6 +30,10 @@ class CertPolicy { // remember the user's choice. void Allow(const net::X509Certificate& cert, net::CertStatus error); + // Returns true if and only if there exists a user allow exception for some + // certificate. + bool HasAllowException() const { return allowed_.size() > 0; } + private: // The set of fingerprints of allowed certificates. std::map<net::SHA256HashValue, net::CertStatus, net::SHA256HashValueLessThan> @@ -65,6 +69,16 @@ class AwSSLHostStateDelegate : public content::SSLHostStateDelegate { bool DidHostRunInsecureContent(const std::string& host, int pid) const override; + // Revokes all SSL certificate error allow exceptions made by the user for + // |host|. + void RevokeUserAllowExceptions(const std::string& host) override; + + // Returns whether the user has allowed a certificate error exception for + // |host|. This does not mean that *all* certificate errors are allowed, just + // that there exists an exception. To see if a particular certificate and + // error combination exception is allowed, use QueryPolicy(). + bool HasAllowException(const std::string& host) const override; + private: // Certificate policies for each host. std::map<std::string, internal::CertPolicy> cert_policy_for_host_; diff --git a/chrome/browser/ssl/chrome_ssl_host_state_delegate.h b/chrome/browser/ssl/chrome_ssl_host_state_delegate.h index e4e6480..b680740 100644 --- a/chrome/browser/ssl/chrome_ssl_host_state_delegate.h +++ b/chrome/browser/ssl/chrome_ssl_host_state_delegate.h @@ -41,7 +41,7 @@ class ChromeSSLHostStateDelegate : public content::SSLHostStateDelegate { // Revokes all SSL certificate error allow exceptions made by the user for // |host| in the given Profile. - virtual void RevokeUserAllowExceptions(const std::string& host); + void RevokeUserAllowExceptions(const std::string& host) override; // RevokeUserAllowExceptionsHard is the same as RevokeUserAllowExceptions but // additionally may close idle connections in the process. This should be used @@ -53,7 +53,7 @@ class ChromeSSLHostStateDelegate : public content::SSLHostStateDelegate { // |host|. This does not mean that *all* certificate errors are allowed, just // that there exists an exception. To see if a particular certificate and // error combination exception is allowed, use QueryPolicy(). - virtual bool HasAllowException(const std::string& host) const; + bool HasAllowException(const std::string& host) const override; protected: // SetClock takes ownership of the passed in clock. diff --git a/chrome/browser/ssl/ssl_browser_tests.cc b/chrome/browser/ssl/ssl_browser_tests.cc index e7ded66..59da844 100644 --- a/chrome/browser/ssl/ssl_browser_tests.cc +++ b/chrome/browser/ssl/ssl_browser_tests.cc @@ -21,6 +21,7 @@ #include "chrome/browser/safe_browsing/ping_manager.h" #include "chrome/browser/safe_browsing/safe_browsing_service.h" #include "chrome/browser/safe_browsing/ui_manager.h" +#include "chrome/browser/ssl/chrome_ssl_host_state_delegate.h" #include "chrome/browser/ssl/ssl_blocking_page.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_commands.h" @@ -41,6 +42,7 @@ #include "content/public/browser/navigation_entry.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/render_frame_host.h" +#include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/render_widget_host_view.h" #include "content/public/browser/web_contents.h" @@ -50,13 +52,19 @@ #include "content/public/test/browser_test_utils.h" #include "content/public/test/download_test_observer.h" #include "content/public/test/test_renderer_host.h" +#include "net/base/host_port_pair.h" #include "net/base/net_errors.h" #include "net/base/test_data_directory.h" #include "net/cert/cert_status_flags.h" +#include "net/cert/test_root_certs.h" #include "net/cert/x509_certificate.h" +#include "net/dns/host_resolver.h" +#include "net/dns/mock_host_resolver.h" +#include "net/http/http_transaction_factory.h" #include "net/ssl/ssl_info.h" #include "net/test/spawned_test_server/spawned_test_server.h" #include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_context_getter.h" #if defined(USE_NSS_CERTS) #include "chrome/browser/net/nss_context.h" @@ -265,6 +273,33 @@ class MockSSLCertReporter : public SSLCertReporter { } // namespace CertificateReporting +void RootCertsChangedOnIOThread( + const scoped_refptr<net::URLRequestContextGetter> context_getter) { + net::CertDatabase::GetInstance()->NotifyObserversOfCACertChanged(NULL); + context_getter->GetURLRequestContext() + ->http_transaction_factory() + ->GetSession() + ->CloseAllConnections(); +} + +// Alerts the URLRequestContext for the given WebContents that a root +// certificate has changed state or been removed. This, in turn, clears any +// cached certificate validation in the cert verifier. This will also close all +// connections in the socket pool of |contents|, so calls to this should be made +// with care. +void RootCertsChanged(WebContents* contents) { + scoped_refptr<net::URLRequestContextGetter> url_request_context = + contents->GetBrowserContext()->GetRequestContextForRenderProcess( + contents->GetRenderProcessHost()->GetID()); + base::RunLoop run_loop; + content::BrowserThread::PostTaskAndReply( + content::BrowserThread::IO, FROM_HERE, + base::Bind(&RootCertsChangedOnIOThread, url_request_context), + run_loop.QuitClosure()); + run_loop.Run(); + base::RunLoop().RunUntilIdle(); +} + } // namespace class SSLUITest : public InProcessBrowserTest { @@ -438,11 +473,11 @@ class SSLUITest : public InProcessBrowserTest { } static bool GetPageWithUnsafeWorkerPath( - const net::SpawnedTestServer& expired_https_server, + const net::SpawnedTestServer& https_server, std::string* page_with_unsafe_worker_path) { // Get the "imported.js" URL from the expired https server and // substitute it into the unsafe_worker.js file. - GURL imported_js_url = expired_https_server.GetURL("files/ssl/imported.js"); + GURL imported_js_url = https_server.GetURL("files/ssl/imported.js"); std::vector<net::SpawnedTestServer::StringPair> replacement_text_for_unsafe_worker; replacement_text_for_unsafe_worker.push_back( @@ -2032,32 +2067,93 @@ IN_PROC_BROWSER_TEST_F(SSLUITest, TestUnsafeContentsInWorkerFiltered) { CheckAuthenticatedState(tab, AuthState::NONE); } -IN_PROC_BROWSER_TEST_F(SSLUITest, TestUnsafeContentsInWorker) { +// This test, and the related test TestUnsafeContentsWithUserException, verify +// that if unsafe content is loaded but the host of that unsafe content has a +// user exception, the content runs and the security style remains +// authenticated. This is not necessarily the behavior that should exist, but it +// is verification that it does behave that way. See https://crbug.com/477868 +// for more inforamtion on this. +IN_PROC_BROWSER_TEST_F(SSLUITest, TestUnsafeContentsInWorkerWithUserException) { ASSERT_TRUE(https_server_.Start()); - ASSERT_TRUE(https_server_expired_.Start()); + // Note that it is necessary to user https_server_mismatched_ here over the + // other invalid cert servers. This is because the test relies on the two + // servers having different hosts since SSL exceptions are per-host, not per + // origin, and https_server_mismatched_ uses 'localhost' rather than + // '127.0.0.1'. + ASSERT_TRUE(https_server_mismatched_.Start()); // Navigate to an unsafe site. Proceed with interstitial page to indicate // the user approves the bad certificate. - ui_test_utils::NavigateToURL(browser(), - https_server_expired_.GetURL("files/ssl/blank_page.html")); + ui_test_utils::NavigateToURL( + browser(), https_server_mismatched_.GetURL("files/ssl/blank_page.html")); WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents(); - CheckAuthenticationBrokenState( - tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL); + CheckAuthenticationBrokenState(tab, net::CERT_STATUS_COMMON_NAME_INVALID, + AuthState::SHOWING_INTERSTITIAL); ProceedThroughInterstitial(tab); - CheckAuthenticationBrokenState( - tab, net::CERT_STATUS_DATE_INVALID, AuthState::NONE); + CheckAuthenticationBrokenState(tab, net::CERT_STATUS_COMMON_NAME_INVALID, + AuthState::NONE); // Navigate to safe page that has Worker loading unsafe content. // Expect content to load but be marked as auth broken due to running insecure // content. std::string page_with_unsafe_worker_path; - ASSERT_TRUE(GetPageWithUnsafeWorkerPath(https_server_expired_, + ASSERT_TRUE(GetPageWithUnsafeWorkerPath(https_server_mismatched_, &page_with_unsafe_worker_path)); - ui_test_utils::NavigateToURL(browser(), https_server_.GetURL( - page_with_unsafe_worker_path)); + ui_test_utils::NavigateToURL( + browser(), https_server_.GetURL(page_with_unsafe_worker_path)); CheckWorkerLoadResult(tab, true); // Worker loads insecure content - CheckAuthenticationBrokenState( - tab, CertError::NONE, AuthState::RAN_INSECURE_CONTENT); + CheckAuthenticatedState(tab, CertError::NONE); +} + +// Visits a page with unsafe content and makes sure that if a user exception to +// the certificate error is present, the image is loaded and script executes. +// +// See the comment above SSLUITest.TestUnsafeContentsInWorkerWithUserException +// for a discussion about the desired behavior. +IN_PROC_BROWSER_TEST_F(SSLUITest, TestUnsafeContentsWithUserException) { + ASSERT_TRUE(https_server_.Start()); + // Note that it is necessary to user https_server_mismatched_ here over the + // other invalid cert servers. This is because the test relies on the two + // servers having different hosts since SSL exceptions are per-host, not per + // origin, and https_server_mismatched_ uses 'localhost' rather than + // '127.0.0.1'. + ASSERT_TRUE(https_server_mismatched_.Start()); + + // Navigate to an unsafe site. Proceed with interstitial page to indicate + // the user approves the bad certificate. + ui_test_utils::NavigateToURL( + browser(), https_server_mismatched_.GetURL("files/ssl/blank_page.html")); + WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents(); + CheckAuthenticationBrokenState(tab, net::CERT_STATUS_COMMON_NAME_INVALID, + AuthState::SHOWING_INTERSTITIAL); + ProceedThroughInterstitial(tab); + CheckAuthenticationBrokenState(tab, net::CERT_STATUS_COMMON_NAME_INVALID, + AuthState::NONE); + + std::string replacement_path; + ASSERT_TRUE(GetFilePathWithHostAndPortReplacement( + "files/ssl/page_with_unsafe_contents.html", + https_server_mismatched_.host_port_pair(), &replacement_path)); + ui_test_utils::NavigateToURL(browser(), + https_server_.GetURL(replacement_path)); + + // When the bad content is filtered, the state is expected to be + // authenticated. + CheckAuthenticatedState(tab, AuthState::NONE); + + int img_width; + EXPECT_TRUE(content::ExecuteScriptAndExtractInt( + tab, "window.domAutomationController.send(ImageWidth());", &img_width)); + // In order to check that the image was loaded, we check its width. + // The actual image (Google logo) is 114 pixels wide, so we assume a good + // image is greater than 100. + EXPECT_GT(img_width, 100); + + bool js_result = false; + EXPECT_TRUE(content::ExecuteScriptAndExtractBool( + tab, "window.domAutomationController.send(IsFooSet());", &js_result)); + EXPECT_TRUE(js_result); + CheckAuthenticatedState(tab, CertError::NONE); } // Test that when the browser blocks displaying insecure content (images), the @@ -2248,6 +2344,40 @@ IN_PROC_BROWSER_TEST_F(SSLUITest, InterstitialNotAffectedByHideShow) { EXPECT_TRUE(tab->GetRenderWidgetHostView()->IsShowing()); } +// Verifies that if a bad certificate is seen for a host and the user proceeds +// through the interstitial, the decision to proceed is initially remembered. +// However, if this is followed by another visit, and a good certificate +// is seen for the same host, the original exception is forgotten. +IN_PROC_BROWSER_TEST_F(SSLUITest, BadCertFollowedByGoodCert) { + ASSERT_TRUE(https_server_.Start()); + std::string https_server_host = + https_server_.GetURL("files/ssl/google.html").host(); + + WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents(); + net::TestRootCerts* root_certs = net::TestRootCerts::GetInstance(); + + ASSERT_TRUE(root_certs); + root_certs->Clear(); + + Profile* profile = Profile::FromBrowserContext(tab->GetBrowserContext()); + ChromeSSLHostStateDelegate* state = + reinterpret_cast<ChromeSSLHostStateDelegate*>( + profile->GetSSLHostStateDelegate()); + + ui_test_utils::NavigateToURL(browser(), + https_server_.GetURL("files/ssl/google.html")); + + ProceedThroughInterstitial(tab); + EXPECT_TRUE(state->HasAllowException(https_server_host)); + + ASSERT_TRUE(https_server_.LoadTestRootCert()); + RootCertsChanged(tab); + ui_test_utils::NavigateToURL(browser(), + https_server_.GetURL("files/ssl/google.html")); + ASSERT_FALSE(tab->GetInterstitialPage()); + EXPECT_FALSE(state->HasAllowException(https_server_host)); +} + class SSLBlockingPageIDNTest : public SecurityInterstitialIDNTest { protected: // SecurityInterstitialIDNTest implementation diff --git a/content/browser/ssl/ssl_policy.cc b/content/browser/ssl/ssl_policy.cc index 610f741..5a627fb 100644 --- a/content/browser/ssl/ssl_policy.cc +++ b/content/browser/ssl/ssl_policy.cc @@ -8,6 +8,7 @@ #include "base/bind.h" #include "base/command_line.h" #include "base/memory/singleton.h" +#include "base/metrics/histogram_macros.h" #include "base/strings/string_piece.h" #include "base/strings/string_util.h" #include "content/browser/frame_host/navigation_entry_impl.h" @@ -26,6 +27,16 @@ namespace content { +namespace { + +// Events for UMA. Do not reorder or change! +enum SSLGoodCertSeenEvent { + NO_PREVIOUS_EXCEPTION = 0, + HAD_PREVIOUS_EXCEPTION = 1, + SSL_GOOD_CERT_SEEN_EVENT_MAX = 2 +}; +} + SSLPolicy::SSLPolicy(SSLPolicyBackend* backend) : backend_(backend) { DCHECK(backend_); @@ -110,8 +121,20 @@ void SSLPolicy::OnRequestStarted(SSLRequestInfo* info) { // this information back through WebKit and out some FrameLoaderClient // methods. - if (net::IsCertStatusError(info->ssl_cert_status())) + if (net::IsCertStatusError(info->ssl_cert_status())) { backend_->HostRanInsecureContent(info->url().host(), info->child_id()); + } else { + SSLGoodCertSeenEvent event = NO_PREVIOUS_EXCEPTION; + if (backend_->HasAllowException(info->url().host())) { + // If there's no certificate error, a good certificate has been seen, so + // clear out any exceptions that were made by the user for bad + // certificates. + backend_->RevokeUserAllowExceptions(info->url().host()); + event = HAD_PREVIOUS_EXCEPTION; + } + UMA_HISTOGRAM_ENUMERATION("interstitial.ssl.good_cert_seen", event, + SSL_GOOD_CERT_SEEN_EVENT_MAX); + } } void SSLPolicy::UpdateEntry(NavigationEntryImpl* entry, diff --git a/content/browser/ssl/ssl_policy_backend.cc b/content/browser/ssl/ssl_policy_backend.cc index 5c65874..a2626da 100644 --- a/content/browser/ssl/ssl_policy_backend.cc +++ b/content/browser/ssl/ssl_policy_backend.cc @@ -31,6 +31,20 @@ bool SSLPolicyBackend::DidHostRunInsecureContent(const std::string& host, return ssl_host_state_delegate_->DidHostRunInsecureContent(host, pid); } +void SSLPolicyBackend::RevokeUserAllowExceptions(const std::string& host) { + if (!ssl_host_state_delegate_) + return; + + ssl_host_state_delegate_->RevokeUserAllowExceptions(host); +} + +bool SSLPolicyBackend::HasAllowException(const std::string& host) { + if (!ssl_host_state_delegate_) + return false; + + return ssl_host_state_delegate_->HasAllowException(host); +} + void SSLPolicyBackend::AllowCertForHost(const net::X509Certificate& cert, const std::string& host, net::CertStatus error) { diff --git a/content/browser/ssl/ssl_policy_backend.h b/content/browser/ssl/ssl_policy_backend.h index 15ebe31..ed50c24 100644 --- a/content/browser/ssl/ssl_policy_backend.h +++ b/content/browser/ssl/ssl_policy_backend.h @@ -27,6 +27,13 @@ class SSLPolicyBackend { // Returns whether the specified host ran insecure content. bool DidHostRunInsecureContent(const std::string& host, int pid) const; + // Revokes all allow exceptions by the user for |host|. + void RevokeUserAllowExceptions(const std::string& host); + + // Returns true if and only if a user exception has previously been made for + // |host|. + bool HasAllowException(const std::string& host); + // Records that |cert| is permitted to be used for |host| in the future, for // a specific error type. void AllowCertForHost(const net::X509Certificate& cert, diff --git a/content/public/browser/ssl_host_state_delegate.h b/content/public/browser/ssl_host_state_delegate.h index 4666471..39589a5 100644 --- a/content/public/browser/ssl_host_state_delegate.h +++ b/content/public/browser/ssl_host_state_delegate.h @@ -54,6 +54,16 @@ class SSLHostStateDelegate { virtual bool DidHostRunInsecureContent(const std::string& host, int pid) const = 0; + // Revokes all SSL certificate error allow exceptions made by the user for + // |host|. + virtual void RevokeUserAllowExceptions(const std::string& host) = 0; + + // Returns whether the user has allowed a certificate error exception for + // |host|. This does not mean that *all* certificate errors are allowed, just + // that there exists an exception. To see if a particular certificate and + // error combination exception is allowed, use QueryPolicy(). + virtual bool HasAllowException(const std::string& host) const = 0; + protected: virtual ~SSLHostStateDelegate() {} }; diff --git a/net/test/spawned_test_server/base_test_server.cc b/net/test/spawned_test_server/base_test_server.cc index 3eeb685..b8ded1f 100644 --- a/net/test/spawned_test_server/base_test_server.cc +++ b/net/test/spawned_test_server/base_test_server.cc @@ -310,6 +310,24 @@ bool BaseTestServer::GetFilePathWithReplacements( return true; } +bool BaseTestServer::LoadTestRootCert() const { + TestRootCerts* root_certs = TestRootCerts::GetInstance(); + if (!root_certs) + return false; + + // Should always use absolute path to load the root certificate. + base::FilePath root_certificate_path = certificates_dir_; + if (!certificates_dir_.IsAbsolute()) { + base::FilePath src_dir; + if (!PathService::Get(base::DIR_SOURCE_ROOT, &src_dir)) + return false; + root_certificate_path = src_dir.Append(certificates_dir_); + } + + return root_certs->AddFromFile( + root_certificate_path.AppendASCII("root_ca_cert.pem")); +} + void BaseTestServer::Init(const std::string& host) { host_port_pair_ = HostPortPair(host, 0); @@ -352,24 +370,6 @@ bool BaseTestServer::ParseServerData(const std::string& server_data) { return true; } -bool BaseTestServer::LoadTestRootCert() const { - TestRootCerts* root_certs = TestRootCerts::GetInstance(); - if (!root_certs) - return false; - - // Should always use absolute path to load the root certificate. - base::FilePath root_certificate_path = certificates_dir_; - if (!certificates_dir_.IsAbsolute()) { - base::FilePath src_dir; - if (!PathService::Get(base::DIR_SOURCE_ROOT, &src_dir)) - return false; - root_certificate_path = src_dir.Append(certificates_dir_); - } - - return root_certs->AddFromFile( - root_certificate_path.AppendASCII("root_ca_cert.pem")); -} - bool BaseTestServer::SetupWhenServerStarted() { DCHECK(host_port_pair_.port()); diff --git a/net/test/spawned_test_server/base_test_server.h b/net/test/spawned_test_server/base_test_server.h index 5fd57f1..d430b0e 100644 --- a/net/test/spawned_test_server/base_test_server.h +++ b/net/test/spawned_test_server/base_test_server.h @@ -262,6 +262,10 @@ class BaseTestServer { ws_basic_auth_ = ws_basic_auth; } + // Marks the root certificate of an HTTPS test server as trusted for + // the duration of tests. + bool LoadTestRootCert() const WARN_UNUSED_RESULT; + protected: virtual ~BaseTestServer(); Type type() const { return type_; } @@ -300,10 +304,6 @@ class BaseTestServer { private: void Init(const std::string& host); - // Marks the root certificate of an HTTPS test server as trusted for - // the duration of tests. - bool LoadTestRootCert() const WARN_UNUSED_RESULT; - // Document root of the test server. base::FilePath document_root_; diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml index efb9be2..2e0e9c7 100644 --- a/tools/metrics/histograms/histograms.xml +++ b/tools/metrics/histograms/histograms.xml @@ -13096,6 +13096,14 @@ Therefore, the affected-histogram name has to have at least one dot in it. </summary> </histogram> +<histogram name="interstitial.ssl.good_cert_seen" enum="SSLGoodCertSeenEvent"> + <owner>jww@chromium.org</owner> + <summary> + Emitted when a good certificate is seen, specifying whether the user already + gave an exception for a bad certificate for the same host. + </summary> +</histogram> + <histogram name="interstitial.ssl.severity_score.authority_invalid" units="%"> <obsolete> Deprecated Jan 2015 (M42). @@ -62454,6 +62462,11 @@ To add a new entry, add it with any value and run test to compute valid value. <int value="9" label="UNKNOWN"/> </enum> +<enum name="SSLGoodCertSeenEvent" type="int"> + <int value="0" label="NO_PREVIOUS_EXCEPTION"/> + <int value="1" label="HAD_PREVIOUS_EXCEPTION"/> +</enum> + <enum name="SSLIsExpiredAndDecision" type="int"> <int value="0" label="EXPIRED_AND_PROCEED"/> <int value="1" label="EXPIRED_AND_DO_NOT_PROCEED"/> |