AuroraRuntime/Source/Crypto/X509/x509.cpp

518 lines
19 KiB
C++

/***
Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved.
File: x509.cpp
Date: 2021-6-12
Author: Reece
***/
#include <Source/RuntimeInternal.hpp>
#include "../Crypto.hpp"
#include "x509.hpp"
#include <mbedtls/x509.h>
#include <mbedtls/oid.h>
//#include <mbedtls/certs.h>
#include <mbedtls/x509_crt.h>
#include <mbedtls/oid.h>
#include <mbedtls/asn1.h>
#define AURORA_OID_ID_AD MBEDTLS_OID_PKIX "\x30"
#define AURORA_OID_ID_PE_ONE MBEDTLS_OID_PKIX "\x01"
#define AURORA_OID_ID_PE_ONE_AIA AURORA_OID_ID_PE_ONE "\x01"
#define AURORA_OID_ID_AD_CA_ISSUERS AURORA_OID_ID_AD "\x02"
#define AURORA_OID_ID_AD_OCSP AURORA_OID_ID_AD "\x01"
namespace Aurora::Crypto::X509
{
#pragma region functions copied from mbedtls, modified to do extract the asn fields we care about
static int x509_get_crt_ext(mbedtls_x509_crt *crt, const char *oid, int oidLength, AuFunction<void(mbedtls_x509_buf &ex, unsigned char **, unsigned char *)> cb);
static int x509_get_ca_id(mbedtls_x509_crt *crt, AuByteBuffer &key)
{
bool ok = false;
return x509_get_crt_ext(crt, MBEDTLS_OID_AUTHORITY_KEY_IDENTIFIER, sizeof(MBEDTLS_OID_AUTHORITY_KEY_IDENTIFIER) - 1, [&](mbedtls_x509_buf &ex, unsigned char **p, unsigned char *end)
{
int ret = 0;
size_t len;
if ((ret = mbedtls_asn1_get_tag(p, end, &len,
MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) != 0)
{
ok = false;
return;
}
auto tag = **p;
(*p)++;
if ((ret = mbedtls_asn1_get_len(p, end, &len)) != 0)
{
ok = false;
return;
}
if (!AuTryResize(key, len))
{
ok = false;
return;
}
memcpy(key.data(), *p, key.size());
ok = true;
}) == 0 && ok;
}
static int x509_get_subject_id(mbedtls_x509_crt *crt, AuByteBuffer &key)
{
bool ok = false;
return x509_get_crt_ext(crt, MBEDTLS_OID_SUBJECT_KEY_IDENTIFIER, sizeof(MBEDTLS_OID_SUBJECT_KEY_IDENTIFIER) - 1, [&](mbedtls_x509_buf &ex, unsigned char **p, unsigned char *end)
{
int ret = 0;
size_t len;
auto tag = **p;
(*p)++;
if ((ret = mbedtls_asn1_get_len(p, end, &len)) != 0)
{
ok = false;
return;
}
if (!AuTryResize(key, len))
{
ok = false;
return;
}
memcpy(key.data(), *p, key.size());
ok = true;
}) == 0 && ok;
}
static int x509_get_aia(mbedtls_x509_crt *crt, AuList<AuString> &ocsp, AuList<AuString> &caIssers)
{
bool ok = false;
return x509_get_crt_ext(crt, AURORA_OID_ID_PE_ONE_AIA, sizeof(AURORA_OID_ID_PE_ONE_AIA) - 1, [&](mbedtls_x509_buf &ex, unsigned char **p, unsigned char *end)
{
int ret = 0;
size_t len, sublen;
/*
AuthorityInfoAccessSyntax ::=
SEQUENCE SIZE (1..MAX) OF AccessDescription
*/
if ((ret = mbedtls_asn1_get_tag(p, end, &len,
MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) != 0)
{
ok = false;
return;
}
while (*p != end)
{
/*
AccessDescription ::= SEQUENCE {
accessMethod OBJECT IDENTIFIER,
accessLocation GeneralName }
*/
auto endOfResource = *p + len;
if ((ret = mbedtls_asn1_get_tag(p, endOfResource, &sublen,
MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) != 0)
{
ok = false;
return;
}
// access method id
mbedtls_x509_buf oid = { 0, 0, NULL };
if ((ret = mbedtls_asn1_get_tag(p, *p + sublen, &oid.len,
MBEDTLS_ASN1_OID)) != 0)
{
ok = false;
return;
}
oid.tag = MBEDTLS_ASN1_OID;
oid.p = *p;
*p += oid.len;
// general name
mbedtls_x509_buf name = { 0, 0, NULL };
auto tag = **p;
(*p)++;
if ((ret = mbedtls_asn1_get_len(p, endOfResource, &name.len)) != 0)
{
ok = false;
return;
}
if ((tag & MBEDTLS_ASN1_TAG_CLASS_MASK) !=
MBEDTLS_ASN1_CONTEXT_SPECIFIC)
{
ok = false;
return;
}
name.tag = tag;
name.p = *p;
if (oid.len == sizeof(AURORA_OID_ID_AD_OCSP) - 1)
{
if (memcmp(oid.p, AURORA_OID_ID_AD_OCSP, oid.len) == 0)
{
ocsp.push_back(AuString(reinterpret_cast<const char *>(name.p), reinterpret_cast<const char *>(name.p) + name.len));
}
}
if (oid.len == sizeof(AURORA_OID_ID_AD_CA_ISSUERS) - 1)
{
if (memcmp(oid.p, AURORA_OID_ID_AD_CA_ISSUERS, oid.len) == 0)
{
caIssers.push_back(AuString(reinterpret_cast<const char *>(name.p), reinterpret_cast<const char *>(name.p) + name.len));
}
}
*p += name.len;
}
}) == 0 && ok;
}
static int x509_get_crt_ext(mbedtls_x509_crt *crt, const char *oid, int oidLength, AuFunction<void(mbedtls_x509_buf &ex, unsigned char **, unsigned char *)> cb)
{
int ret = 0;
size_t len;
unsigned char *end_ext_data, *start_ext_octet, *end_ext_octet;
unsigned char *scre = crt->v3_ext.p;
unsigned char **p = &scre;
auto end = crt->v3_ext.p + crt->v3_ext.len;
if ((ret = mbedtls_asn1_get_tag(p, end, &len,
MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) != 0)
return(MBEDTLS_ERR_X509_INVALID_EXTENSIONS + ret);
if (end != *p + len)
return(MBEDTLS_ERR_X509_INVALID_EXTENSIONS +
MBEDTLS_ERR_ASN1_LENGTH_MISMATCH);
while (*p < end)
{
/*
* Extension ::= SEQUENCE {
* extnID OBJECT IDENTIFIER,
* critical BOOLEAN DEFAULT FALSE,
* extnValue OCTET STRING }
*/
mbedtls_x509_buf extn_oid = { 0, 0, NULL };
int is_critical = 0; /* DEFAULT FALSE */
int ext_type = 0;
if ((ret = mbedtls_asn1_get_tag(p, end, &len,
MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) != 0)
return(MBEDTLS_ERR_X509_INVALID_EXTENSIONS + ret);
end_ext_data = *p + len;
/* Get extension ID */
if ((ret = mbedtls_asn1_get_tag(p, end_ext_data, &extn_oid.len,
MBEDTLS_ASN1_OID)) != 0)
return(MBEDTLS_ERR_X509_INVALID_EXTENSIONS + ret);
extn_oid.tag = MBEDTLS_ASN1_OID;
extn_oid.p = *p;
*p += extn_oid.len;
/* Get optional critical */
if ((ret = mbedtls_asn1_get_bool(p, end_ext_data, &is_critical)) != 0 &&
(ret != MBEDTLS_ERR_ASN1_UNEXPECTED_TAG))
return(MBEDTLS_ERR_X509_INVALID_EXTENSIONS + ret);
/* Data should be octet string type */
if ((ret = mbedtls_asn1_get_tag(p, end_ext_data, &len,
MBEDTLS_ASN1_OCTET_STRING)) != 0)
return(MBEDTLS_ERR_X509_INVALID_EXTENSIONS + ret);
start_ext_octet = *p;
end_ext_octet = *p + len;
if (end_ext_octet != end_ext_data)
return(MBEDTLS_ERR_X509_INVALID_EXTENSIONS +
MBEDTLS_ERR_ASN1_LENGTH_MISMATCH);
if (extn_oid.len != oidLength)
{
*p = end_ext_octet;
continue;
}
if (memcmp(extn_oid.p, oid, oidLength))
{
*p = end_ext_octet;
continue;
}
cb(extn_oid, p, end_ext_octet);
return 0;
}
if (*p != end)
return(MBEDTLS_ERR_X509_INVALID_EXTENSIONS +
MBEDTLS_ERR_ASN1_LENGTH_MISMATCH);
return MBEDTLS_ERR_X509_INVALID_EXTENSIONS;
}
template<int C>
static bool find_oid_value_in_name(const mbedtls_x509_name *name, const char(&oid)[C], AuString &value)
{
const char *short_name = NULL;
size_t retval = 0;
while (name != NULL)
{
if (!name->oid.p)
{
name = name->next;
continue;
}
if (name->oid.len != (C - 1))
{
name = name->next;
continue;
}
if (memcmp(oid, name->oid.p, (C - 1)))
{
name = name->next;
continue;
}
value = AuString(reinterpret_cast<const char *>(name->val.p), reinterpret_cast<const char *>(name->val.p) + name->val.len);
return true;
}
return false;
}
#pragma endregion
static void FindCommonNames(const mbedtls_x509_name &name, CertName &out)
{
find_oid_value_in_name(&name, MBEDTLS_OID_AT_CN, out.commonName);
find_oid_value_in_name(&name, MBEDTLS_OID_AT_COUNTRY, out.countryCode);
find_oid_value_in_name(&name, MBEDTLS_OID_AT_ORGANIZATION, out.organization);
find_oid_value_in_name(&name, MBEDTLS_OID_AT_ORG_UNIT, out.department);
find_oid_value_in_name(&name, MBEDTLS_OID_AT_STATE, out.state);
find_oid_value_in_name(&name, MBEDTLS_OID_PKCS9_EMAIL, out.email);
find_oid_value_in_name(&name, MBEDTLS_OID_AT_TITLE, out.title);
find_oid_value_in_name(&name, MBEDTLS_OID_AT_GIVEN_NAME, out.name);
find_oid_value_in_name(&name, MBEDTLS_OID_AT_POSTAL_CODE, out.postcode);
find_oid_value_in_name(&name, MBEDTLS_OID_AT_POSTAL_ADDRESS, out.address);
find_oid_value_in_name(&name, MBEDTLS_OID_AT_LOCALITY, out.locality);
}
static bool ParseCert(const Certificate &der, AuFunction<void(mbedtls_x509_crt &crt)> cb)
{
bool ret = false;
mbedtls_x509_crt crt {};
mbedtls_x509_crt_init(&crt);
auto status = mbedtls_x509_crt_parse(&crt,
reinterpret_cast<const unsigned char *>(der.data()),
der.size());
if (status < 0) goto out;
cb(crt);
ret = true;
out:
mbedtls_x509_crt_free(&crt);
return ret;
}
static AuInt64 ConvertTime(const mbedtls_x509_time &time)
{
AuTime::tm tm = {};
tm.tm_year = time.year - 1900;
tm.tm_mon = time.mon - 1;
tm.tm_mday = time.day;
tm.tm_sec = time.sec;
tm.tm_min = time.min;
tm.tm_hour = time.hour;
return AuTime::FromCivilTime(tm, AuTime::ETimezoneShift::eUTC);
}
static void FindUsage(DecodedCertificate &out,
const mbedtls_x509_sequence *extended_key_usage)
{
const mbedtls_x509_sequence *pCur = extended_key_usage;
while (pCur)
{
#define CHECK_CHECK(enum, oid) \
if (pCur->buf.len == sizeof(oid) - 1) \
{ \
if (!AuMemcmp(oid, pCur->buf.p, pCur->buf.len)) \
{ \
out.usage.push_back(enum); \
} \
}
CHECK_CHECK(EExtendedUsage::eServerAuth, MBEDTLS_OID_SERVER_AUTH);
CHECK_CHECK(EExtendedUsage::eClientAuth, MBEDTLS_OID_CLIENT_AUTH);
CHECK_CHECK(EExtendedUsage::eCodeSigning, MBEDTLS_OID_CODE_SIGNING);
CHECK_CHECK(EExtendedUsage::eEmailProtection, MBEDTLS_OID_EMAIL_PROTECTION);
CHECK_CHECK(EExtendedUsage::eTimeStamping, MBEDTLS_OID_TIME_STAMPING);
CHECK_CHECK(EExtendedUsage::eOCSPSigning, MBEDTLS_OID_OCSP_SIGNING);
#undef CHECK_CHECK
pCur = pCur->next;
}
}
void DecodeInternal(const mbedtls_x509_crt &crt, DecodedCertificate &out)
{
auto &issuer = crt.issuer;
auto &subject = crt.subject;
out.version = crt.version;
out.iMaxPath = crt.private_max_pathlen;
out.bIsCA = crt.private_ca_istrue;
FindUsage(out, &crt.ext_key_usage);
FindCommonNames(issuer, out.issuer);
FindCommonNames(subject, out.subject);
out.validity.issued = ConvertTime(crt.valid_from);
out.validity.expire = ConvertTime(crt.valid_to);
x509_get_ca_id((mbedtls_x509_crt *)&crt, out.issuer.id);
x509_get_subject_id((mbedtls_x509_crt *)&crt, out.subject.id);
out.serialNumber.resize(crt.serial.len);
memcpy(out.serialNumber.data(), crt.serial.p, out.serialNumber.size());
out.algorithmOid.resize(crt.sig_oid.len);
memcpy(out.algorithmOid.data(), crt.sig_oid.p, out.algorithmOid.size());
AuList<AuString> oscp;
x509_get_aia((mbedtls_x509_crt *)&crt, oscp, out.AIAs);
}
AUKN_SYM bool Decode(const Certificate &der, DecodedCertificate &out)
{
return ParseCert(der,
[&](mbedtls_x509_crt &crt)
{
DecodeInternal(crt, out);
});
}
static bool IsHighRiskStateIssuer(const mbedtls_x509_crt &ca)
{
AuString issuer;
if (!find_oid_value_in_name(&ca.issuer, MBEDTLS_OID_AT_COUNTRY, issuer))
{
// FAIL!
return true;
}
if (issuer.empty())
{
// FAIL!
return true;
}
// Winnie the Pooh has been caught with his paws in the honey jar one too many times
// At least other nation state attacks have been covert enough for us to not know about any *TLS* MITM attacks originating from them
// Pro-china cuckolds at mozilla suggested we merely block certs issued issued by compromised cn CAs using their issue date. lol no
// https://arstechnica.com/information-technology/2015/03/google-warns-of-unauthorized-tls-certificates-trusted-by-almost-all-oses/
// https://www.zdnet.com/article/china-is-now-blocking-all-encrypted-https-traffic-using-tls-1-3-and-esni/
// https://www.popularmechanics.com/military/news/a28510/china-secret-plan-invade-taiwan/
// https://www.theguardian.com/world/2021/mar/10/china-could-invade-taiwan-in-next-six-years-top-us-admiral-warns
// https://en.wikipedia.org/wiki/Lazarus_Group (use chinese connections)
if ((!gRuntimeConfig.crypto.allowChineseCerts) &&
(stricmp(issuer.c_str(), "cn") == 0) || // mainland and countries the communists decided are theirs. often used by the dprk
(stricmp(issuer.c_str(), "mo") == 0) || // macau, china special admin region
(stricmp(issuer.c_str(), "hk") == 0) || // hong kong
/*(stricmp(issuer.c_str(), "tw") == 0)*/ false) // tehe yes we will be ready to invade by 2020. i suppose taiwan is safe enough for.now
{
SysPushErrorCrypt("The funny western yellow bear in the east is behind you");
return true;
}
// The intersection between technical threats and countries we can't do business with legally
// https://en.wikipedia.org/wiki/United_States_sanctions
// https://en.wikipedia.org/wiki/Communications_in_Iran
// https://www.gov.uk/government/collections/financial-sanctions-regime-specific-consolidated-lists-and-releases
// https://en.wikipedia.org/wiki/Lazarus_Group
// https://www.bbc.co.uk/news/stories-57520169
if ((stricmp(issuer.c_str(), "ir") == 0) || // iran - has own intranet. IXPs are owned by the state
(stricmp(issuer.c_str(), "iq") == 0) || // iraq
(stricmp(issuer.c_str(), "kp") == 0)) // north korea - has own intranet. IXPs are owned by the state
{
SysPushErrorCrypt("Service is unavailable in your country for legal and/or technical safety concerns");
return true;
}
// Russia is somewhat misunderstood and have their hands tied in a lot of cases, not to excuse blatent corruption
// Moscow wants MSK-IX IXP to be independent and have ran tests to validate their intranet could work without the rest of the world
// I wonder if Pavel Durov would trust russian certificates, probably not, right? Major states, especially Russia, shouldn't be trusted with foreign services
// allowRussianCerts is true by default; however, services targeting a market of close political enemies of russia should consider disallowing russian certs
// Further, private infrastructure around Russian territories should consider a heightened global pinning security policy
if ((!gRuntimeConfig.crypto.allowRussianCerts) &&
(stricmp(issuer.c_str(), "ru") == 0))
{
SysPushErrorCrypt("Service is unavailable in your country for technical safety concerns");
return true;
}
// The 5 eyes are smart enough to know MITM attacks would send alarms ringing at major services providers
// They are known to MITM plain text traffic and hoard vulns; however, we can design around these issues
// Do not use NIST curves, do not use plain text across public IXPs, don't save data in plain text in EU/US datacenters, etc
// There is nothing we can do about western powers. They seem to be playing... not fair... but smart
{
// obama bin listening
}
return false;
}
AUKN_SYM bool Validate(const Certificate &der, const Certificate &parentDer)
{
bool failed = false;
// gross
return ParseCert(der,
[&](mbedtls_x509_crt &crt)
{
ParseCert(parentDer,
[&](mbedtls_x509_crt &ca)
{
if ((failed |= IsHighRiskStateIssuer(ca)))
{
return;
}
failed |= 0 > mbedtls_x509_crt_verify_restartable(&crt, &ca, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
});
}) && !failed;
}
}