Commit 993771f0 authored by Pawel Polanski's avatar Pawel Polanski
Browse files

Capabilities and IMEIs can now be extracted form Dev Certs for SymbianOS

Reviewed-by: Alessandro Portale
parent df43d55e
......@@ -40,7 +40,16 @@ CertificatePathChooser::CertificatePathChooser(QWidget *parent) :
bool CertificatePathChooser::validatePath(const QString &path, QString *errorMessage)
{
if (Utils::PathChooser::validatePath(path, errorMessage))
return S60CertificateInfo::validateCertificate(path, errorMessage) == S60CertificateInfo::CertificateValid;
if (Utils::PathChooser::validatePath(path, errorMessage)) {
QScopedPointer<S60CertificateInfo> certInfoPtr(new S60CertificateInfo(path));
if (certInfoPtr.data()->validateCertificate() == S60CertificateInfo::CertificateValid) {
if (errorMessage)
*errorMessage = certInfoPtr.data()->toHtml();
return true;
} else {
if (errorMessage)
*errorMessage = certInfoPtr.data()->errorString();
}
}
return false;
}
......@@ -32,42 +32,161 @@
#include <QDateTime>
#include <QFileInfo>
#include <QCoreApplication>
#include <QTextStream>
#include "s60symbiancertificate.h"
S60CertificateInfo::CertificateState S60CertificateInfo::validateCertificate(const QString &certFilePath, QString *errorString)
namespace {
const char * const SIMPLE_DATE_FORMAT = "dd.MM.yyyy";
}
struct Capability {
const char *name;
const int value;
};
static const Capability capability[] =
{
{ "LocalServices", S60CertificateInfo::LocalServices },
{ "Location", S60CertificateInfo::Location },
{ "NetworkServices", S60CertificateInfo::NetworkServices },
{ "ReadUserData", S60CertificateInfo::ReadUserData },
{ "UserEnvironment", S60CertificateInfo::UserEnvironment },
{ "WriteUserData", S60CertificateInfo::WriteUserData },
{ "PowerMgmt", S60CertificateInfo::PowerMgmt },
{ "ProtServ", S60CertificateInfo::ProtServ },
{ "ReadDeviceData", S60CertificateInfo::ReadDeviceData },
{ "SurroundingsDD", S60CertificateInfo::SurroundingsDD },
{ "SwEvent", S60CertificateInfo::SwEvent },
{ "TrustedUI", S60CertificateInfo::TrustedUI },
{ "WriteDeviceData", S60CertificateInfo::WriteDeviceData },
{ "CommDD", S60CertificateInfo::CommDD },
{ "DiskAdmin", S60CertificateInfo::DiskAdmin },
{ "NetworkControl", S60CertificateInfo::NetworkControl },
{ "MultimediaDD", S60CertificateInfo::MultimediaDD },
{ "AllFiles", S60CertificateInfo::AllFiles },
{ "DRM", S60CertificateInfo::DRM },
{ "TCB", S60CertificateInfo::TCB }
};
QStringList createCapabilityList(uint capabilities)
{
const int capabilityCount = sizeof(capability)/sizeof(capability[0]);
QStringList capabilityList;
for(int i = 0; i < capabilityCount; ++i)
if (capabilities&capability[i].value)
capabilityList << QLatin1String(capability[i].name);
return capabilityList;
}
S60CertificateInfo::S60CertificateInfo(const QString &filePath, QObject* parent)
: QObject(parent),
m_certificate(new S60SymbianCertificate(filePath)),
m_filePath(filePath)
{
}
S60CertificateInfo::~S60CertificateInfo()
{
delete m_certificate;
}
S60CertificateInfo::CertificateState S60CertificateInfo::validateCertificate()
{
CertificateState result = CertificateValid;
S60SymbianCertificate *certificate = new S60SymbianCertificate(certFilePath);
if (certificate->isValid()) {
if (m_certificate->isValid()) {
QDateTime currentTime(QDateTime::currentDateTimeUtc());
QDateTime endTime(certificate->endTime());
QDateTime startTime(certificate->startTime());
QDateTime endTime(m_certificate->endTime());
QDateTime startTime(m_certificate->startTime());
if (currentTime > endTime) {
if (errorString)
*errorString = QCoreApplication::translate(
"S60Utils::validateCertificate",
"The \"%1\" certificate has already expired and cannot be used.\nExpiration date: %2.")
.arg(QFileInfo(certFilePath).fileName())
.arg(endTime.toLocalTime().toString());
m_errorString = tr("The \"%1\" certificate has already expired and cannot be used."
"\nExpiration date: %2.")
.arg(QFileInfo(m_filePath).fileName())
.arg(endTime.toLocalTime().toString(QLatin1String(SIMPLE_DATE_FORMAT)));
result = CertificateError;
} else if (currentTime < startTime) {
if (errorString)
*errorString = QCoreApplication::translate(
"S60Utils::validateCertificate",
"The \"%1\" certificate is not yet valid.\nValid from: %2.")
.arg(QFileInfo(certFilePath).fileName())
.arg(startTime.toLocalTime().toString());
m_errorString = tr("The \"%1\" certificate is not yet valid.\nValid from: %2.")
.arg(QFileInfo(m_filePath).fileName())
.arg(startTime.toLocalTime().toString(QLatin1String(SIMPLE_DATE_FORMAT)));
result = CertificateWarning; //This certificate may be valid in the near future
}
} else {
if (errorString)
*errorString = QCoreApplication::translate(
"S60Utils::validateCertificate",
"The \"%1\" certificate is not a valid X.509 certificate.")
.arg(QFileInfo(certFilePath).baseName());
m_errorString = tr("The \"%1\" certificate is not a valid X.509 certificate.")
.arg(QFileInfo(m_filePath).baseName());
result = CertificateError;
}
delete certificate;
return result;
}
QString S60CertificateInfo::errorString() const
{
return m_errorString.isEmpty()?m_certificate->errorString():m_errorString;
}
quint32 S60CertificateInfo::capabilitiesSupported()
{
return NoInformation;
}
QString S60CertificateInfo::toHtml()
{
const QStringList capabilityList(m_certificate->subjectInfo(QLatin1String("1.2.826.0.1.1796587.1.1.1.6")));
QString htmlString;
QTextStream str(&htmlString);
str << "<html><body><table>"
<< "<tr><td><b>" << tr("Type: ") << "</b></td>";
if (!capabilityList.isEmpty())
str << "<td>" << tr("Developer certificate") << "</td>";
if (m_certificate->isSelfSigned())
str << "<td>" << tr("Self signed certificate") << "</td>";
str << "</tr>";
QString issuer;
QStringList issuerOrganizationList(m_certificate->issuerInfo("X520.Organization"));
if (!issuerOrganizationList.isEmpty())
issuer = issuerOrganizationList.join(QString(" "));
QString subject;
QStringList subjectOrganizationList(m_certificate->subjectInfo("X520.Organization"));
if (!subjectOrganizationList.isEmpty())
subject = subjectOrganizationList.join(QString(" "));
QDateTime startDate(m_certificate->startTime().toLocalTime());
QDateTime endDate(m_certificate->endTime().toLocalTime());
str << "<tr><td><b>" << tr("Issued by: ")
<< "</b></td><td>" << issuer << "</td></tr>"
<< "<tr><td><b>" << tr("Issued to: ")
<< "</b></td><td>" << subject << "</td></tr>"
<< "<tr><td><b>" << tr("Valid from: ")
<< "</b></td><td>" << startDate.toString(QLatin1String(SIMPLE_DATE_FORMAT)) << "</td></tr>"
<< "<tr><td><b>" << tr("Valid to: ")
<< "</b></td><td>" << endDate.toString(QLatin1String(SIMPLE_DATE_FORMAT)) << "</td></tr>";
if (!capabilityList.isEmpty()) {
bool isOk(false);
quint32 capabilities = capabilityList.at(0).toLong(&isOk);
if (isOk) {
str << "<tr><td><b>" << tr("Capabilities: ")
<< "</b></td><td>" << createCapabilityList(capabilities).join(" ") << "</td></tr>";
}
}
const QStringList imeiList(m_certificate->subjectInfo(QLatin1String("1.2.826.0.1.1796587.1.1.1.1")));
if (!imeiList.isEmpty()) {
QString imeiListString;
QString space(" ");
int MAX_DISPLAYED_IMEI_COUNT = 30;
if (imeiList.count() > MAX_DISPLAYED_IMEI_COUNT) {//1000 items would be too much :)
for (int i = 0; i < MAX_DISPLAYED_IMEI_COUNT; ++i)
imeiListString += imeiList.at(i) + space;
imeiListString.replace(imeiListString.length()-1, 1, QString("..."));
} else
imeiListString = imeiList.join(space);
str << "<tr><td><b>" << tr("Supporting %n device(s): ", "", imeiList.count())
<< "</b></td><td>" << imeiListString << "</td></tr>";
}
return htmlString;
}
......@@ -30,19 +30,60 @@
#ifndef S60CERTIFICATEINFO_H
#define S60CERTIFICATEINFO_H
#include <QtCore/QObject>
#include <QtCore/QtGlobal>
QT_FORWARD_DECLARE_CLASS(QString)
QT_FORWARD_DECLARE_CLASS(S60SymbianCertificate)
class S60CertificateInfo
class S60CertificateInfo : public QObject
{
Q_OBJECT
public:
enum CertificateState {
CertificateValid,
CertificateWarning,
CertificateError
};
static CertificateState validateCertificate(const QString &certFilePath, QString *errorString = 0);
enum S60Capability {
TCB = 1 << (31-0),
CommDD = 1 << (31-1),
PowerMgmt = 1 << (31-2),
MultimediaDD = 1 << (31-3),
ReadDeviceData = 1 << (31-4),
WriteDeviceData = 1 << (31-5),
DRM = 1 << (31-6),
TrustedUI = 1 << (31-7),
ProtServ = 1 << (31-8),
DiskAdmin = 1 << (31-9),
NetworkControl = 1 << (31-10),
AllFiles = 1 << (31-11),
SwEvent = 1 << (31-12),
NetworkServices = 1 << (31-13),
LocalServices = 1 << (31-14),
ReadUserData = 1 << (31-15),
WriteUserData = 1 << (31-16),
Location = 1 << (31-17),
SurroundingsDD = 1 << (31-18),
UserEnvironment = 1 << (31-19),
NoInformation = 0
};
explicit S60CertificateInfo(const QString &filePath, QObject* parent = 0);
~S60CertificateInfo();
CertificateState validateCertificate();
quint32 capabilitiesSupported();
QString toHtml();
QString errorString() const;
private:
S60SymbianCertificate *m_certificate;
QString m_filePath;
QString m_errorString;
};
#endif // S60CERTIFICATEINFO_H
......@@ -469,20 +469,20 @@ bool S60CreatePackageStep::validateCustomSigningResources()
ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM));
return false;
}
S60CertificateInfo::CertificateState certState = S60CertificateInfo::validateCertificate(customSignaturePath(), &errorString);
QScopedPointer<S60CertificateInfo> certInfoPtr(new S60CertificateInfo(customSignaturePath()));
S60CertificateInfo::CertificateState certState = certInfoPtr.data()->validateCertificate();
switch (certState) {
case S60CertificateInfo::CertificateError:
emit addOutput(errorString, BuildStep::ErrorMessageOutput);
emit addOutput(certInfoPtr.data()->errorString(), BuildStep::ErrorMessageOutput);
emit addTask(ProjectExplorer::Task(ProjectExplorer::Task::Error,
errorString,
certInfoPtr.data()->errorString(),
QString(), -1,
ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM));
return false;
case S60CertificateInfo::CertificateWarning:
emit addOutput(errorString, BuildStep::MessageOutput);
emit addOutput(certInfoPtr.data()->errorString(), BuildStep::MessageOutput);
emit addTask(ProjectExplorer::Task(ProjectExplorer::Task::Warning,
errorString,
certInfoPtr.data()->errorString(),
QString(), -1,
ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM));
break;
......
......@@ -38,10 +38,275 @@
#include <botan/bigint.h>
#include <botan/oids.h>
#include <botan/pem.h>
#include <botan/sha160.h>
#include <botan/oids.h>
#include <botan/libstate.h>
#include <botan/bit_ops.h>
#include <algorithm>
#include <memory>
using namespace Botan;
namespace {
const char * const CERT_IMEI_FIELD_NAME = "1.2.826.0.1.1796587.1.1.1.1";
const char * const CERT_CAPABILITY_FIELD_NAME = "1.2.826.0.1.1796587.1.1.1.6";
}
// ======== S60CertificateExtension
/*
* X.509 S60 Certificate Extension
*/
class S60CertificateExtension : Certificate_Extension
{
public:
OID oid_of() const;
virtual S60CertificateExtension* copy() const = 0;
virtual ~S60CertificateExtension() {}
protected:
friend class S60Extensions;
virtual bool should_encode() const { return false; }
virtual MemoryVector<byte> encode_inner() const = 0;
virtual void decode_inner(const MemoryRegion<byte>&) = 0;
};
// ======== S60DeviceIdListConstraint
class S60DeviceIdListConstraint : public S60CertificateExtension
{
public:
S60CertificateExtension* copy() const { return new S60DeviceIdListConstraint(imei_list); }
S60DeviceIdListConstraint() {}
S60DeviceIdListConstraint(const std::vector<ASN1_String>& o) : imei_list(o) {}
std::vector<ASN1_String> get_imei_list() const { return imei_list; }
private:
std::string config_id() const { return "deviceid_list_constraint"; }
std::string oid_name() const { return CERT_IMEI_FIELD_NAME; }
MemoryVector<byte> encode_inner() const;
void decode_inner(const MemoryRegion<byte>&);
void contents_to(Data_Store&, Data_Store&) const;
std::vector<ASN1_String> imei_list;
};
/*
* Encode the extension
*/
MemoryVector<byte> S60DeviceIdListConstraint::encode_inner() const
{
qFatal("Encoding S60 extensions is not supported.");
return MemoryVector<byte>();
}
/*
* Decode the extension
*/
#include "botan/hex.h"
void S60DeviceIdListConstraint::decode_inner(const MemoryRegion<byte>& in)
{
BER_Decoder(in)
.start_cons(SEQUENCE)
.decode_list(imei_list)
.end_cons();
}
/*
* Return a textual representation
*/
void S60DeviceIdListConstraint::contents_to(Data_Store& subject, Data_Store&) const
{
for(u32bit j = 0; j != imei_list.size(); ++j)
subject.add(CERT_IMEI_FIELD_NAME, imei_list[j].value());
}
// ======== S60CapabilityConstraint
class S60CapabilityConstraint : public S60CertificateExtension
{
public:
S60CertificateExtension* copy() const { return new S60CapabilityConstraint(capabilities); }
S60CapabilityConstraint() {}
S60CapabilityConstraint(const SecureVector<byte>& o) : capabilities(o) {}
SecureVector<byte> get_capability_list() const { return capabilities; }
private:
std::string config_id() const { return "capability_constraint"; }
std::string oid_name() const { return "CERT_CAPABILITY_FIELD_NAME"; }
MemoryVector<byte> encode_inner() const;
void decode_inner(const MemoryRegion<byte>&);
void contents_to(Data_Store&, Data_Store&) const;
SecureVector<byte> capabilities;
};
/*
* Encode the extension
*/
MemoryVector<byte> S60CapabilityConstraint::encode_inner() const
{
qFatal("Encoding S60 extensions is not supported.");
return MemoryVector<byte>();
}
/*
* Decode the extension
*/
void S60CapabilityConstraint::decode_inner(const MemoryRegion<byte>& in)
{
BER_Decoder(in)
.decode(capabilities, BIT_STRING)
.verify_end();
}
/*
* Return a textual representation
*/
void S60CapabilityConstraint::contents_to(Data_Store& subject, Data_Store&) const
{
quint32 capabilitiesValue = 0;
for(u32bit j = 0; j != sizeof(quint32); ++j) {
quint32 capabilitie(capabilities[sizeof(quint32)-1-j]);
capabilitiesValue |= capabilitie << 8*j;
}
subject.add(CERT_CAPABILITY_FIELD_NAME, capabilitiesValue);
}
// ======== S60Extensions
class S60Extensions : public ASN1_Object
{
public:
void encode_into(class DER_Encoder&) const;
void decode_from(class BER_Decoder&);
void contents_to(Data_Store&, Data_Store&) const;
void add(Certificate_Extension* extn)
{ extensions.push_back(extn); }
S60Extensions& operator=(const S60Extensions&);
S60Extensions(const S60Extensions&);
S60Extensions(bool st = true) : should_throw(st) {}
~S60Extensions();
private:
static Certificate_Extension* get_extension(const OID&);
std::vector<Certificate_Extension*> extensions;
bool should_throw;
};
/*
* S60Extensions Copy Constructor
*/
S60Extensions::S60Extensions(const S60Extensions& extensions) : ASN1_Object()
{
*this = extensions;
}
/*
* Extensions Assignment Operator
*/
S60Extensions& S60Extensions::operator=(const S60Extensions& other)
{
for(u32bit j = 0; j != extensions.size(); ++j)
delete extensions[j];
extensions.clear();
for(u32bit j = 0; j != other.extensions.size(); ++j)
extensions.push_back(other.extensions[j]->copy());
return (*this);
}
/*
* Return the OID of this extension
*/
OID Certificate_Extension::oid_of() const
{
return OIDS::lookup(oid_name());
}
/*
* Encode an Extensions list
*/
void S60Extensions::encode_into(DER_Encoder& to_object) const
{
Q_UNUSED(to_object);
qFatal("Encoding S60 extensions is not supported.");
}
/*
* Decode a list of Extensions
*/
void S60Extensions::decode_from(BER_Decoder& from_source)
{
for(u32bit j = 0; j != extensions.size(); ++j)
delete extensions[j];
extensions.clear();
BER_Decoder sequence = from_source.start_cons(SEQUENCE);
while(sequence.more_items())
{
OID oid;
MemoryVector<byte> value;
bool critical;
sequence.start_cons(SEQUENCE)
.decode(oid)
.decode_optional(critical, BOOLEAN, UNIVERSAL, false)
.decode(value, OCTET_STRING)
.verify_end()
.end_cons();
S60CertificateExtension* ext = 0;
if (OIDS::name_of(oid, CERT_IMEI_FIELD_NAME))
ext = new S60DeviceIdListConstraint();
if (OIDS::name_of(oid, CERT_CAPABILITY_FIELD_NAME))
ext = new S60CapabilityConstraint();
if(!ext)
{
if(!critical || !should_throw)
continue;
throw Decoding_Error("Encountered unknown X.509 extension marked "
"as critical; OID = " + oid.as_string());
}
ext->decode_inner(value);
extensions.push_back(ext);
}
sequence.verify_end();
}
/*
* Write the extensions to an info store
*/
void S60Extensions::contents_to(Data_Store& subject_info,
Data_Store& issuer_info) const
{
for(u32bit j = 0; j != extensions.size(); ++j)
extensions[j]->contents_to(subject_info, issuer_info);
}
/*
* Delete an Extensions list
*/
S60Extensions::~S60Extensions()
{
for(u32bit j = 0; j != extensions.size(); ++j)
delete extensions[j];
}
// ======== S60SymbianCertificatePrivate
class S60SymbianCertificatePrivate : private Botan::X509_Object
......@@ -242,6 +507,12 @@ void S60SymbianCertificatePrivate::force_decode()
if(v3_exts_data.type_tag == 3 &&
v3_exts_data.class_tag == ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC))
{
S60Extensions s60extensions(false);
BER_Decoder(v3_exts_data.value).decode(s60extensions).verify_end();
s60extensions.contents_to(m_subject, m_issuer);
Extensions