Index: runtime/bin/secure_socket.cc |
diff --git a/runtime/bin/secure_socket.cc b/runtime/bin/secure_socket.cc |
index 2bfa94fa0c728da6c1c9e63eca583cf092258c65..50382660853172b36605cc876bf6efc560764490 100644 |
--- a/runtime/bin/secure_socket.cc |
+++ b/runtime/bin/secure_socket.cc |
@@ -10,12 +10,9 @@ |
#include <stdio.h> |
#include <string.h> |
-#include <certdb.h> |
#include <key.h> |
#include <keyt.h> |
#include <nss.h> |
-#include <p12.h> |
-#include <p12plcy.h> |
#include <pk11pub.h> |
#include <prerror.h> |
#include <prinit.h> |
@@ -40,10 +37,10 @@ namespace bin { |
bool SSLFilter::library_initialized_ = false; |
// To protect library initialization. |
-dart::Mutex* SSLFilter::mutex = new dart::Mutex(); |
+dart::Mutex* SSLFilter::mutex_ = new dart::Mutex(); |
// The password is needed when creating secure server sockets. It can |
// be null if only secure client sockets are used. |
-char* SSLFilter::password_ = NULL; |
+const char* SSLFilter::password_ = NULL; |
// Forward declaration. |
static void ProcessFilter(Dart_Port dest_port_id, |
@@ -232,7 +229,7 @@ void FUNCTION_NAME(SecureSocket_InitializeLibrary) |
&certificate_database)); |
} else if (!Dart_IsNull(certificate_database_object)) { |
Dart_ThrowException(DartUtils::NewDartArgumentError( |
- "SecureSocket.initialize: database argument is not a String or null")); |
+ "Non-String certificate directory argument to SetCertificateDatabase")); |
} |
// Leave certificate_database as NULL if no value was provided. |
@@ -247,7 +244,7 @@ void FUNCTION_NAME(SecureSocket_InitializeLibrary) |
password = ""; |
} else { |
Dart_ThrowException(DartUtils::NewDartArgumentError( |
- "SecureSocket.initialize: password argument is not a String or null")); |
+ "Password argument to SetCertificateDatabase is not a String or null")); |
} |
Dart_Handle builtin_roots_object = |
@@ -258,322 +255,10 @@ void FUNCTION_NAME(SecureSocket_InitializeLibrary) |
ThrowIfError(Dart_BooleanValue(builtin_roots_object, &builtin_roots)); |
} else { |
Dart_ThrowException(DartUtils::NewDartArgumentError( |
- "SecureSocket.initialize: useBuiltinRoots argument is not a bool")); |
+ "UseBuiltinRoots argument to SetCertificateDatabase is not a bool")); |
} |
- Dart_Handle read_only_object = |
- ThrowIfError(Dart_GetNativeArgument(args, 3)); |
- // Check that the type is boolean, and get the boolean value from it. |
- bool read_only = true; |
- if (Dart_IsBoolean(read_only_object)) { |
- ThrowIfError(Dart_BooleanValue(read_only_object, &read_only)); |
- } else { |
- Dart_ThrowException(DartUtils::NewDartArgumentError( |
- "SecureSocket.initialize: readOnly argument is not a bool")); |
- } |
- |
- SSLFilter::InitializeLibrary( |
- certificate_database, password, builtin_roots, read_only); |
-} |
- |
- |
-static Dart_Handle X509FromCertificate(CERTCertificate* certificate) { |
- PRTime start_validity; |
- PRTime end_validity; |
- SECStatus status = |
- CERT_GetCertTimes(certificate, &start_validity, &end_validity); |
- if (status != SECSuccess) { |
- ThrowPRException("CertificateException", |
- "Cannot get validity times from certificate"); |
- } |
- int64_t start_epoch_ms = start_validity / PR_USEC_PER_MSEC; |
- int64_t end_epoch_ms = end_validity / PR_USEC_PER_MSEC; |
- Dart_Handle subject_name_object = |
- DartUtils::NewString(certificate->subjectName); |
- Dart_Handle issuer_name_object = |
- DartUtils::NewString(certificate->issuerName); |
- Dart_Handle start_epoch_ms_int = Dart_NewInteger(start_epoch_ms); |
- Dart_Handle end_epoch_ms_int = Dart_NewInteger(end_epoch_ms); |
- |
- Dart_Handle date_type = |
- DartUtils::GetDartType(DartUtils::kCoreLibURL, "DateTime"); |
- Dart_Handle from_milliseconds = |
- DartUtils::NewString("fromMillisecondsSinceEpoch"); |
- |
- Dart_Handle start_validity_date = |
- Dart_New(date_type, from_milliseconds, 1, &start_epoch_ms_int); |
- Dart_Handle end_validity_date = |
- Dart_New(date_type, from_milliseconds, 1, &end_epoch_ms_int); |
- |
- Dart_Handle x509_type = |
- DartUtils::GetDartType(DartUtils::kIOLibURL, "X509Certificate"); |
- Dart_Handle arguments[] = { subject_name_object, |
- issuer_name_object, |
- start_validity_date, |
- end_validity_date }; |
- return Dart_New(x509_type, Dart_Null(), 4, arguments); |
-} |
- |
- |
-char* PasswordCallback(PK11SlotInfo* slot, PRBool retry, void* arg) { |
- if (!retry) { |
- return PL_strdup(static_cast<char*>(arg)); // Freed by NSS internals. |
- } |
- return NULL; |
-} |
- |
- |
-void FUNCTION_NAME(SecureSocket_AddCertificate) |
- (Dart_NativeArguments args) { |
- Dart_Handle certificate_object = |
- ThrowIfError(Dart_GetNativeArgument(args, 0)); |
- Dart_Handle trust_object = ThrowIfError(Dart_GetNativeArgument(args, 1)); |
- |
- if (!Dart_IsList(certificate_object) || !Dart_IsString(trust_object)) { |
- Dart_ThrowException(DartUtils::NewDartArgumentError( |
- "Bad argument to SecureSocket.addCertificate")); |
- } |
- |
- intptr_t length; |
- ThrowIfError(Dart_ListLength(certificate_object, &length)); |
- uint8_t* certificate = reinterpret_cast<uint8_t*>(malloc(length + 1)); |
- if (certificate == NULL) { |
- FATAL("Out of memory in SecureSocket.addCertificate"); |
- } |
- ThrowIfError(Dart_ListGetAsBytes( |
- certificate_object, 0, certificate, length)); |
- |
- const char* trust_string; |
- ThrowIfError(Dart_StringToCString(trust_object, |
- &trust_string)); |
- |
- PK11SlotInfo* slot = PK11_GetInternalKeySlot(); |
- SECStatus status = PK11_Authenticate(slot, PR_TRUE, SSLFilter::GetPassword()); |
- PK11_FreeSlot(slot); |
- if (status == SECFailure) { |
- ThrowPRException("CertificateException", |
- "Could not authenticate to certificate database"); |
- } |
- |
- CERTCertificate* cert = CERT_DecodeCertFromPackage( |
- reinterpret_cast<char*>(certificate), length); |
- if (cert == NULL) { |
- ThrowPRException("CertificateException", "Certificate cannot be decoded"); |
- } |
- CERTCertTrust trust; |
- status = CERT_DecodeTrustString(&trust, trust_string); |
- if (status != SECSuccess) { |
- ThrowPRException("CertificateException", "Trust string cannot be decoded"); |
- } |
- { |
- MutexLocker locker(SSLFilter::mutex); |
- status = CERT_ChangeCertTrust(CERT_GetDefaultCertDB(), cert, &trust); |
- } |
- if (status != SECSuccess) { |
- ThrowPRException("CertificateException", "Cannot set trust attributes"); |
- } |
- |
- Dart_SetReturnValue(args, X509FromCertificate(cert)); |
- return; |
-} |
- |
- |
-/* |
- * Called by the PKCS#12 decoder if a certificate's nickname collides with |
- * the nickname of a different existing certificate in the database. |
- */ |
-SECItem* nickname_callback(SECItem *old_nickname, |
- PRBool *cancel, |
- void *arg) { |
- *cancel = PR_TRUE; |
- return NULL; |
-} |
- |
- |
-void FUNCTION_NAME(SecureSocket_ImportCertificatesWithPrivateKeys) |
- (Dart_NativeArguments args) { |
- Dart_Handle pk12_object = ThrowIfError(Dart_GetNativeArgument(args, 0)); |
- if (!Dart_IsList(pk12_object)) { |
- Dart_ThrowException(DartUtils::NewDartArgumentError( |
- "SecureSocket.importPrivateCertificates: certificates is not a List")); |
- } |
- |
- Dart_Handle password_object = ThrowIfError(Dart_GetNativeArgument(args, 1)); |
- if (!Dart_IsString(password_object)) { |
- Dart_ThrowException(DartUtils::NewDartArgumentError( |
- "SecureSocket.importPrivateCertificates: password is not a String")); |
- } |
- |
- intptr_t length; |
- ThrowIfError(Dart_ListLength(pk12_object, &length)); |
- uint8_t* pk12 = Dart_ScopeAllocate(length); |
- if (pk12 == NULL) { |
- FATAL("Out of memory in SecureSocket.importPrivateCertificates"); |
- } |
- ThrowIfError(Dart_ListGetAsBytes(pk12_object, 0, pk12, length)); |
- |
- // A big-endian Unicode (UTF16) password. |
- intptr_t password_length; |
- ThrowIfError(Dart_StringLength(password_object, &password_length)); |
- password_length++; |
- uint16_t* password = reinterpret_cast<uint16_t*>( |
- Dart_ScopeAllocate(sizeof(uint16_t) * password_length)); |
- if (password == NULL) { |
- FATAL("Out of memory in SecureSocket.importPrivateCertificates"); |
- } |
- intptr_t returned_length = password_length; |
- ThrowIfError(Dart_StringToUTF16(password_object, password, &returned_length)); |
- ASSERT(password_length == returned_length + 1); |
- password[password_length - 1] = 0; |
- for (int i = 0; i < password_length; ++i) { |
- password[i] = Utils::HostToBigEndian16(password[i]); |
- } |
- SECItem p12_password; |
- p12_password.type = siBuffer; |
- p12_password.data = reinterpret_cast<unsigned char*>(password); |
- p12_password.len = sizeof(uint16_t) * password_length; |
- |
- Dart_SetReturnValue(args, Dart_Null()); |
- // Set the password callback for the certificate database we are importing to. |
- // The password for a slot is gotten from a callback, and it is freed by the |
- // caller of the callback. The argument to the callback comes from the wincx |
- // argument to a PK11 function. |
- PK11SlotInfo* slot = PK11_GetInternalKeySlot(); |
- SECStatus status = PK11_Authenticate(slot, PR_TRUE, SSLFilter::GetPassword()); |
- if (status == SECFailure) { |
- PK11_FreeSlot(slot); |
- ThrowPRException("CertificateException", |
- "Could not authenticate to certificate database"); |
- } |
- |
- SEC_PKCS12DecoderContext* context = SEC_PKCS12DecoderStart( |
- &p12_password, |
- slot, |
- SSLFilter::GetPassword(), |
- NULL, |
- NULL, |
- NULL, |
- NULL, |
- NULL); |
- PK11_FreeSlot(slot); |
- if (!context) { |
- FATAL("Unexpected error: SecureSocket.addPrivateCertificates DecoderStart"); |
- } |
- bool success; |
- { |
- MutexLocker locker(SSLFilter::mutex); |
- success = |
- SECSuccess == SEC_PKCS12DecoderUpdate(context, pk12, length) && |
- SECSuccess == SEC_PKCS12DecoderVerify(context) && |
- SECSuccess == SEC_PKCS12DecoderValidateBags(context, |
- nickname_callback) && |
- SECSuccess == SEC_PKCS12DecoderImportBags(context); |
- } |
- SEC_PKCS12DecoderFinish(context); |
- if (!success) { |
- ThrowPRException("CertificateException", "Could not import PKCS#12 file"); |
- } |
-} |
- |
- |
-void FUNCTION_NAME(SecureSocket_ChangeTrust)(Dart_NativeArguments args) { |
- Dart_Handle nickname_object = ThrowIfError(Dart_GetNativeArgument(args, 0)); |
- if (!Dart_IsString(nickname_object)) { |
- Dart_ThrowException(DartUtils::NewDartArgumentError( |
- "SecureSocket.changeTrust: nickname argument is not a String")); |
- } |
- const char* nickname; |
- ThrowIfError(Dart_StringToCString(nickname_object, &nickname)); |
- |
- Dart_Handle trust_object = ThrowIfError(Dart_GetNativeArgument(args, 1)); |
- if (!Dart_IsString(trust_object)) { |
- Dart_ThrowException(DartUtils::NewDartArgumentError( |
- "SecureSocket.changeTrust: trust argument is not a String")); |
- } |
- const char* trust_string; |
- ThrowIfError(Dart_StringToCString(trust_object, &trust_string)); |
- |
- PK11SlotInfo* slot = PK11_GetInternalKeySlot(); |
- SECStatus status = PK11_Authenticate(slot, PR_TRUE, SSLFilter::GetPassword()); |
- if (status == SECFailure) { |
- ThrowPRException("CertificateException", |
- "Could not authenticate to certificate database"); |
- } |
- PK11_FreeSlot(slot); |
- |
- CERTCertificate* certificate = |
- PK11_FindCertFromNickname(nickname, SSLFilter::GetPassword()); |
- if (certificate == NULL) { |
- ThrowCertificateException("Cannot find certificate with nickname %s", |
- nickname); |
- } |
- CERTCertTrust trust; |
- if (SECSuccess != CERT_DecodeTrustString(&trust, trust_string)) { |
- CERT_DestroyCertificate(certificate); |
- ThrowPRException("CertificateException", "Trust string cannot be decoded"); |
- } |
- { |
- MutexLocker locker(SSLFilter::mutex); |
- status = CERT_ChangeCertTrust(CERT_GetDefaultCertDB(), certificate, &trust); |
- } |
- if (status != SECSuccess) { |
- CERT_DestroyCertificate(certificate); |
- ThrowCertificateException("Cannot set trust on certificate %s", nickname); |
- } |
- Dart_SetReturnValue(args, X509FromCertificate(certificate)); |
- CERT_DestroyCertificate(certificate); |
-} |
- |
- |
-void FUNCTION_NAME(SecureSocket_GetCertificate)(Dart_NativeArguments args) { |
- Dart_Handle nickname_object = ThrowIfError(Dart_GetNativeArgument(args, 0)); |
- if (!Dart_IsString(nickname_object)) { |
- Dart_ThrowException(DartUtils::NewDartArgumentError( |
- "SecureSocket.getCertificate: nickname argument is not a String")); |
- } |
- const char* nickname; |
- ThrowIfError(Dart_StringToCString(nickname_object, &nickname)); |
- |
- CERTCertificate* certificate = PK11_FindCertFromNickname( |
- nickname, SSLFilter::GetPassword()); |
- if (certificate != NULL) { |
- Dart_SetReturnValue(args, X509FromCertificate(certificate)); |
- CERT_DestroyCertificate(certificate); |
- } |
-} |
- |
- |
-void FUNCTION_NAME(SecureSocket_RemoveCertificate)(Dart_NativeArguments args) { |
- Dart_Handle nickname_object = |
- ThrowIfError(Dart_GetNativeArgument(args, 0)); |
- if (!Dart_IsString(nickname_object)) { |
- Dart_ThrowException(DartUtils::NewDartArgumentError( |
- "SecureSocket.removeCertificate: nickname is not a String")); |
- } |
- const char* nickname; |
- ThrowIfError(Dart_StringToCString(nickname_object, &nickname)); |
- |
- CERTCertificate* certificate = |
- PK11_FindCertFromNickname(nickname, SSLFilter::GetPassword()); |
- if (certificate == NULL) { |
- ThrowCertificateException("Cannot find certificate with nickname %s", |
- nickname); |
- } |
- SECKEYPrivateKey* key = |
- PK11_FindKeyByAnyCert(certificate, SSLFilter::GetPassword()); |
- // Free the copy returned from FindKeyByAnyCert. |
- SECKEY_DestroyPrivateKey(key); |
- SECStatus status; |
- { |
- MutexLocker locker(SSLFilter::mutex); |
- status = (key == NULL) ? |
- SEC_DeletePermCertificate(certificate) : |
- PK11_DeleteTokenCertAndKey(certificate, SSLFilter::GetPassword()); |
- } |
- CERT_DestroyCertificate(certificate); |
- if (status != SECSuccess) { |
- ThrowCertificateException("Cannot remove certificate %s", nickname); |
- } |
+ SSLFilter::InitializeLibrary(certificate_database, password, builtin_roots); |
} |
@@ -723,9 +408,47 @@ bool SSLFilter::ProcessAllBuffers(int starts[kNumBuffers], |
} |
+static Dart_Handle X509FromCertificate(CERTCertificate* certificate) { |
+ PRTime start_validity; |
+ PRTime end_validity; |
+ SECStatus status = |
+ CERT_GetCertTimes(certificate, &start_validity, &end_validity); |
+ if (status != SECSuccess) { |
+ ThrowPRException("CertificateException", |
+ "Cannot get validity times from certificate"); |
+ } |
+ int64_t start_epoch_ms = start_validity / PR_USEC_PER_MSEC; |
+ int64_t end_epoch_ms = end_validity / PR_USEC_PER_MSEC; |
+ Dart_Handle subject_name_object = |
+ DartUtils::NewString(certificate->subjectName); |
+ Dart_Handle issuer_name_object = |
+ DartUtils::NewString(certificate->issuerName); |
+ Dart_Handle start_epoch_ms_int = Dart_NewInteger(start_epoch_ms); |
+ Dart_Handle end_epoch_ms_int = Dart_NewInteger(end_epoch_ms); |
+ |
+ Dart_Handle date_type = |
+ DartUtils::GetDartType(DartUtils::kCoreLibURL, "DateTime"); |
+ Dart_Handle from_milliseconds = |
+ DartUtils::NewString("fromMillisecondsSinceEpoch"); |
+ |
+ Dart_Handle start_validity_date = |
+ Dart_New(date_type, from_milliseconds, 1, &start_epoch_ms_int); |
+ Dart_Handle end_validity_date = |
+ Dart_New(date_type, from_milliseconds, 1, &end_epoch_ms_int); |
+ |
+ Dart_Handle x509_type = |
+ DartUtils::GetDartType(DartUtils::kIOLibURL, "X509Certificate"); |
+ Dart_Handle arguments[] = { subject_name_object, |
+ issuer_name_object, |
+ start_validity_date, |
+ end_validity_date }; |
+ return Dart_New(x509_type, Dart_Null(), 4, arguments); |
+} |
+ |
+ |
void SSLFilter::Init(Dart_Handle dart_this) { |
if (!library_initialized_) { |
- InitializeLibrary(NULL, "", true, true, false); |
+ InitializeLibrary(NULL, "", true, false); |
} |
ASSERT(string_start_ == NULL); |
string_start_ = Dart_NewPersistentHandle(DartUtils::NewString("start")); |
@@ -798,6 +521,14 @@ void SSLFilter::RegisterBadCertificateCallback(Dart_Handle callback) { |
} |
+char* PasswordCallback(PK11SlotInfo* slot, PRBool retry, void* arg) { |
+ if (!retry) { |
+ return PL_strdup(static_cast<char*>(arg)); // Freed by NSS internals. |
+ } |
+ return NULL; |
+} |
+ |
+ |
static const char* builtin_roots_module = |
#if defined(TARGET_OS_LINUX) || defined(TARGET_OS_ANDROID) |
"name=\"Root Certs\" library=\"libnssckbi.so\""; |
@@ -814,9 +545,8 @@ static const char* builtin_roots_module = |
void SSLFilter::InitializeLibrary(const char* certificate_database, |
const char* password, |
bool use_builtin_root_certificates, |
- bool read_only, |
bool report_duplicate_initialization) { |
- MutexLocker locker(mutex); |
+ MutexLocker locker(mutex_); |
SECStatus status; |
if (!library_initialized_) { |
PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0); |
@@ -824,7 +554,7 @@ void SSLFilter::InitializeLibrary(const char* certificate_database, |
if (certificate_database == NULL || certificate_database[0] == '\0') { |
status = NSS_NoDB_Init(NULL); |
if (status != SECSuccess) { |
- mutex->Unlock(); // MutexLocker destructor not called when throwing. |
+ mutex_->Unlock(); // MutexLocker destructor not called when throwing. |
ThrowPRException("TlsException", |
"Failed NSS_NoDB_Init call."); |
} |
@@ -832,13 +562,13 @@ void SSLFilter::InitializeLibrary(const char* certificate_database, |
SECMODModule* module = SECMOD_LoadUserModule( |
const_cast<char*>(builtin_roots_module), NULL, PR_FALSE); |
if (!module) { |
- mutex->Unlock(); // MutexLocker destructor not called when throwing. |
+ mutex_->Unlock(); // MutexLocker destructor not called when throwing. |
ThrowPRException("TlsException", |
"Failed to load builtin root certificates."); |
} |
} |
} else { |
- PRUint32 init_flags = read_only ? NSS_INIT_READONLY : 0; |
+ PRUint32 init_flags = NSS_INIT_READONLY; |
if (!use_builtin_root_certificates) { |
init_flags |= NSS_INIT_NOMODDB; |
} |
@@ -848,7 +578,7 @@ void SSLFilter::InitializeLibrary(const char* certificate_database, |
SECMOD_DB, |
init_flags); |
if (status != SECSuccess) { |
- mutex->Unlock(); // MutexLocker destructor not called when throwing. |
+ mutex_->Unlock(); // MutexLocker destructor not called when throwing. |
ThrowPRException("TlsException", |
"Failed NSS_Init call."); |
} |
@@ -857,34 +587,28 @@ void SSLFilter::InitializeLibrary(const char* certificate_database, |
} |
library_initialized_ = true; |
- // Allow encoding and decoding of private keys in PKCS#12 files. |
- SEC_PKCS12EnableCipher(PKCS12_RC2_CBC_40, 1); |
- SEC_PKCS12EnableCipher(PKCS12_DES_EDE3_168, 1); |
- SEC_PKCS12SetPreferredCipher(PKCS12_DES_EDE3_168, 1); |
- |
status = NSS_SetDomesticPolicy(); |
if (status != SECSuccess) { |
- mutex->Unlock(); // MutexLocker destructor not called when throwing. |
+ mutex_->Unlock(); // MutexLocker destructor not called when throwing. |
ThrowPRException("TlsException", |
"Failed NSS_SetDomesticPolicy call."); |
} |
- |
// Enable TLS, as well as SSL3 and SSL2. |
status = SSL_OptionSetDefault(SSL_ENABLE_TLS, PR_TRUE); |
if (status != SECSuccess) { |
- mutex->Unlock(); // MutexLocker destructor not called when throwing. |
+ mutex_->Unlock(); // MutexLocker destructor not called when throwing. |
ThrowPRException("TlsException", |
"Failed SSL_OptionSetDefault enable TLS call."); |
} |
status = SSL_ConfigServerSessionIDCache(0, 0, 0, NULL); |
if (status != SECSuccess) { |
- mutex->Unlock(); // MutexLocker destructor not called when throwing. |
+ mutex_->Unlock(); // MutexLocker destructor not called when throwing. |
ThrowPRException("TlsException", |
"Failed SSL_ConfigServerSessionIDCache call."); |
} |
} else if (report_duplicate_initialization) { |
- mutex->Unlock(); // MutexLocker destructor not called when throwing. |
+ mutex_->Unlock(); // MutexLocker destructor not called when throwing. |
// Like ThrowPRException, without adding an OSError. |
Dart_ThrowException(DartUtils::NewDartIOException("TlsException", |
"Called SecureSocket.initialize more than once", |
@@ -959,20 +683,23 @@ void SSLFilter::Connect(const char* host_name, |
const_cast<char*>(certificate_name)); |
if (certificate == NULL) { |
ThrowCertificateException( |
- "Cannot find server certificate with distinguished name %s", |
+ "Cannot find server certificate by distinguished name: %s", |
certificate_name); |
} |
} else { |
// Look up certificate using the nickname certificate_name. |
certificate = PK11_FindCertFromNickname( |
- const_cast<char*>(certificate_name), GetPassword()); |
+ const_cast<char*>(certificate_name), |
+ static_cast<void*>(const_cast<char*>(password_))); |
if (certificate == NULL) { |
ThrowCertificateException( |
- "Cannot find server certificate with nickname %s", |
+ "Cannot find server certificate by nickname: %s", |
certificate_name); |
} |
} |
- SECKEYPrivateKey* key = PK11_FindKeyByAnyCert(certificate, GetPassword()); |
+ SECKEYPrivateKey* key = PK11_FindKeyByAnyCert( |
+ certificate, |
+ static_cast<void*>(const_cast<char*>(password_))); |
if (key == NULL) { |
CERT_DestroyCertificate(certificate); |
if (PR_GetError() == -8177) { |