Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 "nsCertPicker.h"
#include "MainThreadUtils.h"
#include "ScopedNSSTypes.h"
#include "cert.h"
#include "mozilla/RefPtr.h"
#include "nsCOMPtr.h"
#include "nsICertPickDialogs.h"
#include "nsIDialogParamBlock.h"
#include "nsIInterfaceRequestor.h"
#include "nsIX509CertValidity.h"
#include "nsMemory.h"
#include "nsMsgComposeSecure.h"
#include "nsNSSCertificate.h"
#include "nsNSSComponent.h"
#include "nsNSSDialogHelper.h"
#include "nsNSSHelper.h"
#include "nsNSSCertHelper.h"
#include "nsReadableUtils.h"
#include "nsComponentManagerUtils.h" // for do_CreateInstance
#include "nsString.h"
#include "mozpkix/pkixtypes.h"
#include "mozilla/Maybe.h"
#include "mozilla/Unused.h"
#include "mozilla/intl/AppDateTimeFormat.h"
using namespace mozilla;
// Copied from security/manager/ssl/nsCertTree.cpp
static void PRTimeToLocalDateString(PRTime time, nsAString& result) {
PRExplodedTime explodedTime;
PR_ExplodeTime(time, PR_LocalTimeParameters, &explodedTime);
mozilla::intl::DateTimeFormat::StyleBag style;
style.date = mozilla::Some(mozilla::intl::DateTimeFormat::Style::Long);
style.time = mozilla::Nothing();
mozilla::Unused << intl::AppDateTimeFormat::Format(style, &explodedTime,
result);
}
MOZ_TYPE_SPECIFIC_UNIQUE_PTR_TEMPLATE(UniqueCERTCertNicknames,
CERTCertNicknames, CERT_FreeNicknames)
CERTCertNicknames* getNSSCertNicknamesFromCertList(
const UniqueCERTCertList& certList) {
nsAutoString expiredString, notYetValidString;
nsAutoString expiredStringLeadingSpace, notYetValidStringLeadingSpace;
GetPIPNSSBundleString("NicknameExpired", expiredString);
GetPIPNSSBundleString("NicknameNotYetValid", notYetValidString);
expiredStringLeadingSpace.Append(' ');
expiredStringLeadingSpace.Append(expiredString);
notYetValidStringLeadingSpace.Append(' ');
notYetValidStringLeadingSpace.Append(notYetValidString);
NS_ConvertUTF16toUTF8 aUtf8ExpiredString(expiredStringLeadingSpace);
NS_ConvertUTF16toUTF8 aUtf8NotYetValidString(notYetValidStringLeadingSpace);
return CERT_NicknameStringsFromCertList(
certList.get(), const_cast<char*>(aUtf8ExpiredString.get()),
const_cast<char*>(aUtf8NotYetValidString.get()));
}
nsresult FormatUIStrings(nsIX509Cert* cert, const nsAutoString& nickname,
nsAutoString& nickWithSerial, nsAutoString& details) {
if (!NS_IsMainThread()) {
NS_ERROR("nsNSSCertificate::FormatUIStrings called off the main thread");
return NS_ERROR_NOT_SAME_THREAD;
}
RefPtr<nsMsgComposeSecure> mcs = new nsMsgComposeSecure;
if (!mcs) {
return NS_ERROR_FAILURE;
}
nsAutoString info;
nsAutoString temp1;
nickWithSerial.Append(nickname);
if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoIssuedFor", info))) {
details.Append(info);
details.Append(char16_t(' '));
if (NS_SUCCEEDED(cert->GetSubjectName(temp1)) && !temp1.IsEmpty()) {
details.Append(temp1);
}
details.Append(char16_t('\n'));
}
if (NS_SUCCEEDED(cert->GetSerialNumber(temp1)) && !temp1.IsEmpty()) {
details.AppendLiteral(" ");
if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertDumpSerialNo", info))) {
details.Append(info);
details.AppendLiteral(": ");
}
details.Append(temp1);
nickWithSerial.AppendLiteral(" [");
nickWithSerial.Append(temp1);
nickWithSerial.Append(char16_t(']'));
details.Append(char16_t('\n'));
}
nsCOMPtr<nsIX509CertValidity> validity;
nsresult rv = cert->GetValidity(getter_AddRefs(validity));
if (NS_SUCCEEDED(rv) && validity) {
details.AppendLiteral(" ");
if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoValid", info))) {
details.Append(info);
}
PRTime notBefore;
rv = validity->GetNotBefore(&notBefore);
if (NS_SUCCEEDED(rv)) {
details.Append(char16_t(' '));
if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoFrom", info))) {
details.Append(info);
details.Append(char16_t(' '));
}
PRTimeToLocalDateString(notBefore, temp1);
details.Append(temp1);
}
PRTime notAfter;
rv = validity->GetNotAfter(&notAfter);
if (NS_SUCCEEDED(rv)) {
details.Append(char16_t(' '));
if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoTo", info))) {
details.Append(info);
details.Append(char16_t(' '));
}
PRTimeToLocalDateString(notAfter, temp1);
details.Append(temp1);
}
details.Append(char16_t('\n'));
}
UniqueCERTCertificate nssCert(cert->GetCert());
if (!nssCert) {
return NS_ERROR_FAILURE;
}
nsAutoString firstEmail;
const char* aWalkAddr;
for (aWalkAddr = CERT_GetFirstEmailAddress(nssCert.get()); aWalkAddr;
aWalkAddr = CERT_GetNextEmailAddress(nssCert.get(), aWalkAddr)) {
NS_ConvertUTF8toUTF16 email(aWalkAddr);
if (email.IsEmpty()) continue;
if (firstEmail.IsEmpty()) {
// If the first email address from the subject DN is also present
// in the subjectAltName extension, GetEmailAddresses() will return
// it twice (as received from NSS). Remember the first address so that
// we can filter out duplicates later on.
firstEmail = email;
details.AppendLiteral(" ");
if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoEmail", info))) {
details.Append(info);
details.AppendLiteral(": ");
}
details.Append(email);
} else {
// Append current address if it's different from the first one.
if (!firstEmail.Equals(email)) {
details.AppendLiteral(", ");
details.Append(email);
}
}
}
if (!firstEmail.IsEmpty()) {
// We got at least one email address, so we want a newline
details.Append(char16_t('\n'));
}
if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoIssuedBy", info))) {
details.Append(info);
details.Append(char16_t(' '));
if (NS_SUCCEEDED(cert->GetIssuerName(temp1)) && !temp1.IsEmpty()) {
details.Append(temp1);
}
details.Append(char16_t('\n'));
}
if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoStoredIn", info))) {
details.Append(info);
details.Append(char16_t(' '));
if (NS_SUCCEEDED(cert->GetTokenName(temp1)) && !temp1.IsEmpty()) {
details.Append(temp1);
}
}
// the above produces the following output:
//
// Issued to: $subjectName
// Serial number: $serialNumber
// Valid from: $starting_date to $expiration_date
// Certificate Key usage: $usages
// Email: $address(es)
// Issued by: $issuerName
// Stored in: $token
return rv;
}
NS_IMPL_ISUPPORTS(nsCertPicker, nsICertPickDialogs, nsIUserCertPicker)
nsCertPicker::nsCertPicker() {}
nsCertPicker::~nsCertPicker() {}
nsresult nsCertPicker::Init() {
nsresult rv;
nsCOMPtr<nsISupports> psm = do_GetService("@mozilla.org/psm;1", &rv);
return rv;
}
NS_IMETHODIMP
nsCertPicker::PickCertificate(nsIInterfaceRequestor* ctx,
const nsTArray<nsString>& certNickList,
const nsTArray<nsString>& certDetailsList,
int32_t* selectedIndex, bool* canceled) {
nsresult rv;
uint32_t i;
MOZ_ASSERT(certNickList.Length() == certDetailsList.Length());
const uint32_t count = certNickList.Length();
*canceled = false;
nsCOMPtr<nsIDialogParamBlock> block =
do_CreateInstance(NS_DIALOGPARAMBLOCK_CONTRACTID);
if (!block) return NS_ERROR_FAILURE;
block->SetNumberStrings(1 + count * 2);
for (i = 0; i < count; i++) {
rv = block->SetString(i, ToNewUnicode(certNickList[i]));
NS_ENSURE_SUCCESS(rv, rv);
}
for (i = 0; i < count; i++) {
rv = block->SetString(i + count, ToNewUnicode(certDetailsList[i]));
NS_ENSURE_SUCCESS(rv, rv);
}
rv = block->SetInt(0, count);
NS_ENSURE_SUCCESS(rv, rv);
rv = block->SetInt(1, *selectedIndex);
NS_ENSURE_SUCCESS(rv, rv);
rv = nsNSSDialogHelper::openDialog(
NS_ENSURE_SUCCESS(rv, rv);
int32_t status;
rv = block->GetInt(0, &status);
NS_ENSURE_SUCCESS(rv, rv);
*canceled = (status == 0) ? true : false;
if (!*canceled) {
rv = block->GetInt(1, selectedIndex);
}
return rv;
}
NS_IMETHODIMP nsCertPicker::PickByUsage(nsIInterfaceRequestor* ctx,
const char16_t* selectedNickname,
int32_t certUsage, bool allowInvalid,
bool allowDuplicateNicknames,
const nsAString& emailAddress,
bool* canceled, nsIX509Cert** _retval) {
int32_t selectedIndex = -1;
bool selectionFound = false;
CERTCertListNode* node = nullptr;
nsresult rv = NS_OK;
{
// Iterate over all certs. This assures that user is logged in to all
// hardware tokens.
nsCOMPtr<nsIInterfaceRequestor> ctx = new PipUIContext();
UniqueCERTCertList allcerts(PK11_ListCerts(PK11CertListUnique, ctx));
}
/* find all user certs that are valid for the specified usage */
/* note that we are allowing expired certs in this list */
UniqueCERTCertList certList(CERT_FindUserCertsByUsage(
CERT_GetDefaultCertDB(), (SECCertUsage)certUsage,
!allowDuplicateNicknames, !allowInvalid, ctx));
if (!certList) {
return NS_ERROR_NOT_AVAILABLE;
}
/* if a (non-empty) emailAddress argument is supplied to PickByUsage, */
/* remove non-matching certificates from the candidate list */
if (!emailAddress.IsEmpty()) {
node = CERT_LIST_HEAD(certList);
while (!CERT_LIST_END(node, certList)) {
/* if the cert has at least one e-mail address, check if suitable */
if (CERT_GetFirstEmailAddress(node->cert)) {
RefPtr<nsNSSCertificate> tempCert(new nsNSSCertificate(node->cert));
bool match = false;
rv = tempCert->ContainsEmailAddress(emailAddress, &match);
if (NS_FAILED(rv)) {
return rv;
}
if (!match) {
/* doesn't contain the specified address, so remove from the list */
CERTCertListNode* freenode = node;
node = CERT_LIST_NEXT(node);
CERT_RemoveCertListNode(freenode);
continue;
}
}
node = CERT_LIST_NEXT(node);
}
}
UniqueCERTCertNicknames nicknames(getNSSCertNicknamesFromCertList(certList));
if (!nicknames) {
return NS_ERROR_NOT_AVAILABLE;
}
nsTArray<nsString> certNicknameList(nicknames->numnicknames);
nsTArray<nsString> certDetailsList(nicknames->numnicknames);
int32_t CertsToUse;
for (CertsToUse = 0, node = CERT_LIST_HEAD(certList.get());
!CERT_LIST_END(node, certList.get()) &&
CertsToUse < nicknames->numnicknames;
node = CERT_LIST_NEXT(node)) {
RefPtr<nsNSSCertificate> tempCert(new nsNSSCertificate(node->cert));
if (tempCert) {
nsAutoString i_nickname(
NS_ConvertUTF8toUTF16(nicknames->nicknames[CertsToUse]));
nsAutoString nickWithSerial;
nsAutoString details;
if (!selectionFound) {
/* for the case when selectedNickname refers to a bare nickname */
if (i_nickname == nsDependentString(selectedNickname)) {
selectedIndex = CertsToUse;
selectionFound = true;
}
}
if (NS_SUCCEEDED(
FormatUIStrings(tempCert, i_nickname, nickWithSerial, details))) {
certNicknameList.AppendElement(nickWithSerial);
certDetailsList.AppendElement(details);
if (!selectionFound) {
/* for the case when selectedNickname refers to nickname + serial */
if (nickWithSerial == nsDependentString(selectedNickname)) {
selectedIndex = CertsToUse;
selectionFound = true;
}
}
} else {
// Placeholder, to keep the indexes valid.
certNicknameList.AppendElement(u""_ns);
certDetailsList.AppendElement(u""_ns);
}
++CertsToUse;
}
}
if (certNicknameList.IsEmpty()) {
return NS_ERROR_NOT_AVAILABLE;
}
nsCOMPtr<nsICertPickDialogs> dialogs;
rv = getNSSDialogs(getter_AddRefs(dialogs), NS_GET_IID(nsICertPickDialogs),
NS_CERTPICKDIALOGS_CONTRACTID);
if (NS_SUCCEEDED(rv)) {
// Show the cert picker dialog and get the index of the selected cert.
rv = dialogs->PickCertificate(ctx, certNicknameList, certDetailsList,
&selectedIndex, canceled);
}
if (NS_SUCCEEDED(rv) && !*canceled) {
int32_t i;
for (i = 0, node = CERT_LIST_HEAD(certList); !CERT_LIST_END(node, certList);
++i, node = CERT_LIST_NEXT(node)) {
if (i == selectedIndex) {
RefPtr<nsNSSCertificate> cert = new nsNSSCertificate(node->cert);
cert.forget(_retval);
break;
}
}
}
return rv;
}