blob: e5fc483c0ee4072b7a9a0f45c05969bae0513798 [file] [log] [blame]
/*
* Copyright (C) 2019 Sony Interactive Entertainment Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "OpenSSLHelper.h"
#include <openssl/x509v3.h>
#include <wtf/DateMath.h>
#include <wtf/HexNumber.h>
#include <wtf/Seconds.h>
#include <wtf/text/StringBuilder.h>
namespace OpenSSL {
template<typename> struct deleter;
template<> struct deleter<X509> {
void operator()(X509* x509)
{
if (x509)
X509_free(x509);
}
};
class StackOfGeneralName {
public:
StackOfGeneralName(const X509* x509, int nid)
: m_names { static_cast<STACK_OF(GENERAL_NAME)*>(X509_get_ext_d2i(x509, nid, nullptr, nullptr)) }
{
}
~StackOfGeneralName()
{
if (m_names)
sk_GENERAL_NAME_pop_free(m_names, GENERAL_NAME_free);
}
operator bool() { return m_names; }
int count() { return sk_GENERAL_NAME_num(m_names); }
GENERAL_NAME* item(int i) { return sk_GENERAL_NAME_value(m_names, i); }
private:
STACK_OF(GENERAL_NAME)* m_names { nullptr };
};
class StackOfX509 {
public:
StackOfX509(X509_STORE_CTX* ctx)
: m_certs { X509_STORE_CTX_get1_chain(ctx) }
{
}
~StackOfX509()
{
if (m_certs)
sk_X509_pop_free(m_certs, X509_free);
}
int count() { return sk_X509_num(m_certs); }
X509* item(int i) { return sk_X509_value(m_certs, i); }
private:
STACK_OF(X509)* m_certs { nullptr };
};
class BIO {
public:
BIO()
: m_bio { ::BIO_new(::BIO_s_mem()) }
{
}
BIO(X509* data)
: m_bio { ::BIO_new(::BIO_s_mem()) }
{
::PEM_write_bio_X509(m_bio, data);
}
BIO(const Vector<uint8_t>& data)
: m_bio { ::BIO_new_mem_buf(data.data(), data.size()) }
{
}
~BIO()
{
if (m_bio)
::BIO_free(m_bio);
}
Optional<Vector<uint8_t>> getDataAsVector() const
{
uint8_t* data { nullptr };
auto length = ::BIO_get_mem_data(m_bio, &data);
if (length < 0)
return WTF::nullopt;
Vector<uint8_t> result;
result.append(data, length);
return result;
}
String getDataAsString() const
{
uint8_t* data { nullptr };
auto length = ::BIO_get_mem_data(m_bio, &data);
if (length < 0)
return String();
return String(data, length);
}
std::unique_ptr<X509, deleter<X509>> readX509()
{
return std::unique_ptr<X509, deleter<X509>>(::PEM_read_bio_X509(m_bio, nullptr, 0, nullptr));
}
::BIO* get() { return m_bio; }
private:
::BIO* m_bio { nullptr };
};
static Vector<WebCore::CertificateInfo::Certificate> pemDataFromCtx(X509_STORE_CTX* ctx)
{
Vector<WebCore::CertificateInfo::Certificate> result;
StackOfX509 certs { ctx };
for (int i = 0; i < certs.count(); i++) {
BIO bio(certs.item(i));
if (auto certificate = bio.getDataAsVector())
result.append(WTFMove(*certificate));
else
return { };
}
return result;
}
Optional<WebCore::CertificateInfo> createCertificateInfo(X509_STORE_CTX* ctx)
{
if (!ctx)
return WTF::nullopt;
return WebCore::CertificateInfo(X509_STORE_CTX_get_error(ctx), pemDataFromCtx(ctx));
}
static String toString(const ASN1_STRING* name)
{
unsigned char* data { nullptr };
auto length = ASN1_STRING_to_UTF8(&data, name);
if (length <= 0)
return String();
String result(data, length);
OPENSSL_free(data);
return result;
}
static String getCommonName(const X509* x509)
{
auto subjectName = X509_get_subject_name(x509);
if (!subjectName)
return String();
auto index = X509_NAME_get_index_by_NID(subjectName, NID_commonName, -1);
if (index < 0)
return String();
auto commonNameEntry = X509_NAME_get_entry(subjectName, index);
if (!commonNameEntry)
return String();
auto commonNameEntryData = X509_NAME_ENTRY_get_data(commonNameEntry);
if (!commonNameEntryData)
return String();
return toString(commonNameEntryData);
}
static String getSubjectName(const X509* x509)
{
static const unsigned long flags = (ASN1_STRFLGS_RFC2253 | ASN1_STRFLGS_ESC_QUOTE | XN_FLAG_SEP_CPLUS_SPC | XN_FLAG_DN_REV | XN_FLAG_FN_NONE | XN_FLAG_SPC_EQ) & ~ASN1_STRFLGS_ESC_MSB;
auto subjectName = X509_get_subject_name(x509);
if (!subjectName)
return String();
BIO bio;
auto length = X509_NAME_print_ex(bio.get(), subjectName, 0, flags);
if (length <= 0)
return String();
return bio.getDataAsString();
}
static Optional<Seconds> convertASN1TimeToSeconds(const ASN1_TIME* ans1Time)
{
if (!ans1Time)
return WTF::nullopt;
if ((ans1Time->type != V_ASN1_UTCTIME && ans1Time->type != V_ASN1_GENERALIZEDTIME) || !ans1Time->data)
return WTF::nullopt;
// UTCTIME : YYmmddHHMM[SS]
// GENERALIZEDTIME : YYYYmmddHHMM[SS]
int digitLength = ans1Time->type == V_ASN1_UTCTIME ? 10 : 12;
if (ans1Time->length < digitLength)
return WTF::nullopt;
auto data = ans1Time->data;
for (int i = 0; i < digitLength; i++) {
if (!isASCIIDigit(data[i]))
return WTF::nullopt;
}
struct tm time { };
if (ans1Time->type == V_ASN1_UTCTIME) {
time.tm_year = (data[0] - '0') * 10 + (data[1] - '0');
if (time.tm_year < 50)
time.tm_year += 100;
} else {
time.tm_year = (data[0] - '0') * 1000 + (data[1] - '0') * 100 + (data[2] - '0') * 10 + (data[3] - '0');
time.tm_year -= 1900;
}
data += ans1Time->type == V_ASN1_UTCTIME ? 2 : 4;
time.tm_mon = (data[0] - '0') * 10 + (data[1] - '0') - 1;
if (time.tm_mon < 0 || time.tm_mon > 11)
return WTF::nullopt;
time.tm_mday = (data[2] - '0') * 10 + (data[3] - '0');
time.tm_hour = (data[4] - '0') * 10 + (data[5] - '0');
time.tm_min = (data[6] - '0') * 10 + (data[7] - '0');
if ((ans1Time->length >= digitLength + 2) && isASCIIDigit(data[8]) && isASCIIDigit(data[9]))
time.tm_sec = (data[8] - '0') * 10 + (data[9] - '0');
auto gmtTime = mktime(&time);
auto localTimeOffset = calculateLocalTimeOffset(gmtTime * 1000.0);
return Seconds(gmtTime + (localTimeOffset.offset / 1000.0));
}
static void getSubjectAltName(const X509* x509, Vector<String>& dnsNames, Vector<String>& ipAddresses)
{
StackOfGeneralName sanList(x509, NID_subject_alt_name);
if (!sanList)
return;
auto num = sanList.count();
for (auto i = 0; i < num; i++) {
auto* value = sanList.item(i);
if (!value)
continue;
if (value->type == GEN_DNS) {
auto dnsName = toString(value->d.dNSName);
if (!dnsName.isNull())
dnsNames.append(WTFMove(dnsName));
} else if (value->type == GEN_IPADD) {
auto data = value->d.iPAddress->data;
if (value->d.iPAddress->length == 4)
ipAddresses.append(makeString(data[0], ".", data[1], ".", data[2], ".", data[3]));
else if (value->d.iPAddress->length == 16) {
StringBuilder ipAddress;
for (int i = 0; i < 8; i++) {
ipAddress.append(makeString(hex(data[0] << 8 | data[1], 4)));
if (i != 7)
ipAddress.append(":");
data += 2;
}
ipAddresses.append(ipAddress.toString());
}
}
}
}
Optional<WebCore::CertificateInfo::SummaryInfo> createSummaryInfo(const Vector<uint8_t>& pem)
{
BIO bio { pem };
auto x509 = bio.readX509();
if (!x509)
return WTF::nullopt;
WebCore::CertificateInfo::SummaryInfo summaryInfo;
summaryInfo.subject = getCommonName(x509.get());
if (summaryInfo.subject.isNull())
summaryInfo.subject = getSubjectName(x509.get());
if (auto notBefore = convertASN1TimeToSeconds(X509_get0_notBefore(x509.get())))
summaryInfo.validFrom = *notBefore;
if (auto notAfter = convertASN1TimeToSeconds(X509_get0_notAfter(x509.get())))
summaryInfo.validUntil = *notAfter;
getSubjectAltName(x509.get(), summaryInfo.dnsNames, summaryInfo.ipAddresses);
return summaryInfo;
}
}