diff options
Diffstat (limited to 'src/VBox/Main/src-server/CertificateImpl.cpp')
-rw-r--r-- | src/VBox/Main/src-server/CertificateImpl.cpp | 590 |
1 files changed, 590 insertions, 0 deletions
diff --git a/src/VBox/Main/src-server/CertificateImpl.cpp b/src/VBox/Main/src-server/CertificateImpl.cpp new file mode 100644 index 00000000..e346a337 --- /dev/null +++ b/src/VBox/Main/src-server/CertificateImpl.cpp @@ -0,0 +1,590 @@ +/* $Id: CertificateImpl.cpp $ */ +/** @file + * ICertificate COM class implementations. + */ + +/* + * Copyright (C) 2008-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#define LOG_GROUP LOG_GROUP_MAIN_CERTIFICATE +#include <iprt/err.h> +#include <iprt/path.h> +#include <iprt/cpp/utils.h> +#include <VBox/com/array.h> +#include <iprt/crypto/x509.h> + +#include "ProgressImpl.h" +#include "CertificateImpl.h" +#include "AutoCaller.h" +#include "Global.h" +#include "LoggingNew.h" + +using namespace std; + + +/** + * Private instance data for the #Certificate class. + * @see Certificate::m + */ +struct Certificate::Data +{ + Data() + : fTrusted(false) + , fExpired(false) + , fValidX509(false) + { + RT_ZERO(X509); + } + + ~Data() + { + if (fValidX509) + { + RTCrX509Certificate_Delete(&X509); + RT_ZERO(X509); + fValidX509 = false; + } + } + + /** Whether the certificate is trusted. */ + bool fTrusted; + /** Whether the certificate is trusted. */ + bool fExpired; + /** Valid data in mX509. */ + bool fValidX509; + /** Clone of the X.509 certificate. */ + RTCRX509CERTIFICATE X509; + +private: + Data(const Certificate::Data &rTodo) { AssertFailed(); NOREF(rTodo); } + Data &operator=(const Certificate::Data &rTodo) { AssertFailed(); NOREF(rTodo); return *this; } +}; + + +/////////////////////////////////////////////////////////////////////////////////// +// +// Certificate constructor / destructor +// +// //////////////////////////////////////////////////////////////////////////////// + +DEFINE_EMPTY_CTOR_DTOR(Certificate) + +HRESULT Certificate::FinalConstruct() +{ + return BaseFinalConstruct(); +} + +void Certificate::FinalRelease() +{ + uninit(); + BaseFinalRelease(); +} + +/** + * Initializes a certificate instance. + * + * @returns COM status code. + * @param a_pCert The certificate. + * @param a_fTrusted Whether the caller trusts the certificate or not. + * @param a_fExpired Whether the caller consideres the certificate to be + * expired. + */ +HRESULT Certificate::initCertificate(PCRTCRX509CERTIFICATE a_pCert, bool a_fTrusted, bool a_fExpired) +{ + HRESULT rc = S_OK; + LogFlowThisFuncEnter(); + + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), E_FAIL); + + m = new Data(); + + int vrc = RTCrX509Certificate_Clone(&m->X509, a_pCert, &g_RTAsn1DefaultAllocator); + if (RT_SUCCESS(vrc)) + { + m->fValidX509 = true; + m->fTrusted = a_fTrusted; + m->fExpired = a_fExpired; + autoInitSpan.setSucceeded(); + } + else + rc = Global::vboxStatusCodeToCOM(vrc); + + LogFlowThisFunc(("returns rc=%Rhrc\n", rc)); + return rc; +} + +void Certificate::uninit() +{ + /* Enclose the state transition Ready->InUninit->NotReady */ + AutoUninitSpan autoUninitSpan(this); + if (autoUninitSpan.uninitDone()) + return; + + delete m; + m = NULL; +} + + +/** @name Wrapped ICertificate properties + * @{ + */ + +HRESULT Certificate::getVersionNumber(CertificateVersion_T *aVersionNumber) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + Assert(m->fValidX509); + switch (m->X509.TbsCertificate.T0.Version.uValue.u) + { + case RTCRX509TBSCERTIFICATE_V1: *aVersionNumber = CertificateVersion_V1; break; + case RTCRX509TBSCERTIFICATE_V2: *aVersionNumber = CertificateVersion_V2; break; + case RTCRX509TBSCERTIFICATE_V3: *aVersionNumber = CertificateVersion_V3; break; + default: AssertFailed(); *aVersionNumber = CertificateVersion_Unknown; break; + } + return S_OK; +} + +HRESULT Certificate::getSerialNumber(com::Utf8Str &aSerialNumber) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + Assert(m->fValidX509); + + char szTmp[_2K]; + int vrc = RTAsn1Integer_ToString(&m->X509.TbsCertificate.SerialNumber, szTmp, sizeof(szTmp), 0, NULL); + if (RT_SUCCESS(vrc)) + aSerialNumber = szTmp; + else + return Global::vboxStatusCodeToCOM(vrc); + + return S_OK; +} + +HRESULT Certificate::getSignatureAlgorithmOID(com::Utf8Str &aSignatureAlgorithmOID) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + Assert(m->fValidX509); + aSignatureAlgorithmOID = m->X509.TbsCertificate.Signature.Algorithm.szObjId; + + return S_OK; +} + +HRESULT Certificate::getSignatureAlgorithmName(com::Utf8Str &aSignatureAlgorithmName) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + Assert(m->fValidX509); + return i_getAlgorithmName(&m->X509.TbsCertificate.Signature, aSignatureAlgorithmName); +} + +HRESULT Certificate::getIssuerName(std::vector<com::Utf8Str> &aIssuerName) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + Assert(m->fValidX509); + return i_getX509Name(&m->X509.TbsCertificate.Issuer, aIssuerName); +} + +HRESULT Certificate::getSubjectName(std::vector<com::Utf8Str> &aSubjectName) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + Assert(m->fValidX509); + return i_getX509Name(&m->X509.TbsCertificate.Subject, aSubjectName); +} + +HRESULT Certificate::getFriendlyName(com::Utf8Str &aFriendlyName) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + Assert(m->fValidX509); + + PCRTCRX509NAME pName = &m->X509.TbsCertificate.Subject; + + /* + * Enumerate the subject name and pick interesting attributes we can use to + * form a name more friendly than the RTCrX509Name_FormatAsString output. + */ + const char *pszOrg = NULL; + const char *pszOrgUnit = NULL; + const char *pszGivenName = NULL; + const char *pszSurname = NULL; + const char *pszEmail = NULL; + for (uint32_t i = 0; i < pName->cItems; i++) + { + PCRTCRX509RELATIVEDISTINGUISHEDNAME pRdn = pName->papItems[i]; + for (uint32_t j = 0; j < pRdn->cItems; j++) + { + PCRTCRX509ATTRIBUTETYPEANDVALUE pComponent = pRdn->papItems[j]; + AssertContinue(pComponent->Value.enmType == RTASN1TYPE_STRING); + + /* Select interesting components based on the short RDN prefix + string (easier to read and write than OIDs, for now). */ + const char *pszPrefix = RTCrX509Name_GetShortRdn(&pComponent->Type); + if (pszPrefix) + { + const char *pszUtf8; + int vrc = RTAsn1String_QueryUtf8(&pComponent->Value.u.String, &pszUtf8, NULL); + if (RT_SUCCESS(vrc) && *pszUtf8) + { + if (!strcmp(pszPrefix, "Email")) + pszEmail = pszUtf8; + else if (!strcmp(pszPrefix, "O")) + pszOrg = pszUtf8; + else if (!strcmp(pszPrefix, "OU")) + pszOrgUnit = pszUtf8; + else if (!strcmp(pszPrefix, "S")) + pszSurname = pszUtf8; + else if (!strcmp(pszPrefix, "G")) + pszGivenName = pszUtf8; + } + } + } + } + + if (pszGivenName && pszSurname) + { + if (pszEmail) + aFriendlyName = Utf8StrFmt("%s, %s <%s>", pszSurname, pszGivenName, pszEmail); + else if (pszOrg) + aFriendlyName = Utf8StrFmt("%s, %s (%s)", pszSurname, pszGivenName, pszOrg); + else if (pszOrgUnit) + aFriendlyName = Utf8StrFmt("%s, %s (%s)", pszSurname, pszGivenName, pszOrgUnit); + else + aFriendlyName = Utf8StrFmt("%s, %s", pszSurname, pszGivenName); + } + else if (pszOrg && pszOrgUnit) + aFriendlyName = Utf8StrFmt("%s, %s", pszOrg, pszOrgUnit); + else if (pszOrg) + aFriendlyName = Utf8StrFmt("%s", pszOrg); + else if (pszOrgUnit) + aFriendlyName = Utf8StrFmt("%s", pszOrgUnit); + else + { + /* + * Fall back on unfriendly but accurate. + */ + char szTmp[_8K]; + RT_ZERO(szTmp); + RTCrX509Name_FormatAsString(pName, szTmp, sizeof(szTmp) - 1, NULL); + aFriendlyName = szTmp; + } + + return S_OK; +} + +HRESULT Certificate::getValidityPeriodNotBefore(com::Utf8Str &aValidityPeriodNotBefore) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + Assert(m->fValidX509); + return i_getTime(&m->X509.TbsCertificate.Validity.NotBefore, aValidityPeriodNotBefore); +} + +HRESULT Certificate::getValidityPeriodNotAfter(com::Utf8Str &aValidityPeriodNotAfter) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + Assert(m->fValidX509); + return i_getTime(&m->X509.TbsCertificate.Validity.NotAfter, aValidityPeriodNotAfter); +} + +HRESULT Certificate::getPublicKeyAlgorithmOID(com::Utf8Str &aPublicKeyAlgorithmOID) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + Assert(m->fValidX509); + aPublicKeyAlgorithmOID = m->X509.TbsCertificate.SubjectPublicKeyInfo.Algorithm.Algorithm.szObjId; + return S_OK; +} + +HRESULT Certificate::getPublicKeyAlgorithm(com::Utf8Str &aPublicKeyAlgorithm) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + Assert(m->fValidX509); + return i_getAlgorithmName(&m->X509.TbsCertificate.SubjectPublicKeyInfo.Algorithm, aPublicKeyAlgorithm); +} + +HRESULT Certificate::getSubjectPublicKey(std::vector<BYTE> &aSubjectPublicKey) +{ + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); /* Getting encoded ASN.1 bytes may make changes to X509. */ + return i_getEncodedBytes(&m->X509.TbsCertificate.SubjectPublicKeyInfo.SubjectPublicKey.Asn1Core, aSubjectPublicKey); +} + +HRESULT Certificate::getIssuerUniqueIdentifier(com::Utf8Str &aIssuerUniqueIdentifier) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + return i_getUniqueIdentifier(&m->X509.TbsCertificate.T1.IssuerUniqueId, aIssuerUniqueIdentifier); +} + +HRESULT Certificate::getSubjectUniqueIdentifier(com::Utf8Str &aSubjectUniqueIdentifier) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + return i_getUniqueIdentifier(&m->X509.TbsCertificate.T2.SubjectUniqueId, aSubjectUniqueIdentifier); +} + +HRESULT Certificate::getCertificateAuthority(BOOL *aCertificateAuthority) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aCertificateAuthority = m->X509.TbsCertificate.T3.pBasicConstraints + && m->X509.TbsCertificate.T3.pBasicConstraints->CA.fValue; + + return S_OK; +} + +HRESULT Certificate::getKeyUsage(ULONG *aKeyUsage) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aKeyUsage = m->X509.TbsCertificate.T3.fKeyUsage; + return S_OK; +} + +HRESULT Certificate::getExtendedKeyUsage(std::vector<com::Utf8Str> &aExtendedKeyUsage) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + NOREF(aExtendedKeyUsage); + return E_NOTIMPL; +} + +HRESULT Certificate::getRawCertData(std::vector<BYTE> &aRawCertData) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); /* Getting encoded ASN.1 bytes may make changes to X509. */ + return i_getEncodedBytes(&m->X509.SeqCore.Asn1Core, aRawCertData); +} + +HRESULT Certificate::getSelfSigned(BOOL *aSelfSigned) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + Assert(m->fValidX509); + *aSelfSigned = RTCrX509Certificate_IsSelfSigned(&m->X509); + + return S_OK; +} + +HRESULT Certificate::getTrusted(BOOL *aTrusted) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + Assert(m->fValidX509); + *aTrusted = m->fTrusted; + + return S_OK; +} + +HRESULT Certificate::getExpired(BOOL *aExpired) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + Assert(m->fValidX509); + *aExpired = m->fExpired; + return S_OK; +} + +/** @} */ + +/** @name Wrapped ICertificate methods + * @{ + */ + +HRESULT Certificate::isCurrentlyExpired(BOOL *aResult) +{ + AssertReturnStmt(m->fValidX509, *aResult = TRUE, E_UNEXPECTED); + RTTIMESPEC Now; + *aResult = RTCrX509Validity_IsValidAtTimeSpec(&m->X509.TbsCertificate.Validity, RTTimeNow(&Now)) ? FALSE : TRUE; + return S_OK; +} + +HRESULT Certificate::queryInfo(LONG aWhat, com::Utf8Str &aResult) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + /* Insurance. */ + NOREF(aResult); + return setError(E_FAIL, tr("Unknown item %u"), aWhat); +} + +/** @} */ + + +/** @name Methods extracting COM data from the certificate object + * @{ + */ + +/** + * Translates an algorithm OID into a human readable string, if possible. + * + * @returns S_OK. + * @param a_pAlgId The algorithm. + * @param a_rReturn The return string value. + * @throws std::bad_alloc + */ +HRESULT Certificate::i_getAlgorithmName(PCRTCRX509ALGORITHMIDENTIFIER a_pAlgId, com::Utf8Str &a_rReturn) +{ + const char *pszOid = a_pAlgId->Algorithm.szObjId; + const char *pszName; + if (!pszOid) pszName = ""; + else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_RSA)) pszName = "rsaEncryption"; + else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_MD2_WITH_RSA)) pszName = "md2WithRSAEncryption"; + else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_MD4_WITH_RSA)) pszName = "md4WithRSAEncryption"; + else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_MD5_WITH_RSA)) pszName = "md5WithRSAEncryption"; + else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_SHA1_WITH_RSA)) pszName = "sha1WithRSAEncryption"; + else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_SHA224_WITH_RSA)) pszName = "sha224WithRSAEncryption"; + else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_SHA256_WITH_RSA)) pszName = "sha256WithRSAEncryption"; + else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_SHA384_WITH_RSA)) pszName = "sha384WithRSAEncryption"; + else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_SHA512_WITH_RSA)) pszName = "sha512WithRSAEncryption"; + else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_SHA512T224_WITH_RSA)) pszName = "sha512-224WithRSAEncryption"; + else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_SHA512T256_WITH_RSA)) pszName = "sha512-256WithRSAEncryption"; + else + pszName = pszOid; + a_rReturn = pszName; + return S_OK; +} + +/** + * Formats a X.509 name into a string array. + * + * The name is prefix with a short hand of the relative distinguished name + * type followed by an equal sign. + * + * @returns S_OK. + * @param a_pName The X.509 name. + * @param a_rReturn The return string array. + * @throws std::bad_alloc + */ +HRESULT Certificate::i_getX509Name(PCRTCRX509NAME a_pName, std::vector<com::Utf8Str> &a_rReturn) +{ + if (RTCrX509Name_IsPresent(a_pName)) + { + for (uint32_t i = 0; i < a_pName->cItems; i++) + { + PCRTCRX509RELATIVEDISTINGUISHEDNAME pRdn = a_pName->papItems[i]; + for (uint32_t j = 0; j < pRdn->cItems; j++) + { + PCRTCRX509ATTRIBUTETYPEANDVALUE pComponent = pRdn->papItems[j]; + + AssertReturn(pComponent->Value.enmType == RTASN1TYPE_STRING, + setErrorVrc(VERR_CR_X509_NAME_NOT_STRING, "VERR_CR_X509_NAME_NOT_STRING")); + + /* Get the prefix for this name component. */ + const char *pszPrefix = RTCrX509Name_GetShortRdn(&pComponent->Type); + AssertStmt(pszPrefix, pszPrefix = pComponent->Type.szObjId); + + /* Get the string. */ + const char *pszUtf8; + int vrc = RTAsn1String_QueryUtf8(&pComponent->Value.u.String, &pszUtf8, NULL /*pcch*/); + AssertRCReturn(vrc, setErrorVrc(vrc, "RTAsn1String_QueryUtf8(%u/%u,,) -> %Rrc", i, j, vrc)); + + a_rReturn.push_back(Utf8StrFmt("%s=%s", pszPrefix, pszUtf8)); + } + } + } + return S_OK; +} + +/** + * Translates an ASN.1 timestamp into an ISO timestamp string. + * + * @returns S_OK. + * @param a_pTime The timestamp + * @param a_rReturn The return string value. + * @throws std::bad_alloc + */ +HRESULT Certificate::i_getTime(PCRTASN1TIME a_pTime, com::Utf8Str &a_rReturn) +{ + char szTmp[128]; + if (RTTimeToString(&a_pTime->Time, szTmp, sizeof(szTmp))) + { + a_rReturn = szTmp; + return S_OK; + } + AssertFailed(); + return E_FAIL; +} + +/** + * Translates a X.509 unique identifier to a string. + * + * @returns S_OK. + * @param a_pUniqueId The unique identifier. + * @param a_rReturn The return string value. + * @throws std::bad_alloc + */ +HRESULT Certificate::i_getUniqueIdentifier(PCRTCRX509UNIQUEIDENTIFIER a_pUniqueId, com::Utf8Str &a_rReturn) +{ + /* The a_pUniqueId may not be present! */ + if (RTCrX509UniqueIdentifier_IsPresent(a_pUniqueId)) + { + void const *pvData = RTASN1BITSTRING_GET_BIT0_PTR(a_pUniqueId); + size_t const cbData = RTASN1BITSTRING_GET_BYTE_SIZE(a_pUniqueId); + size_t const cbFormatted = cbData * 3 - 1 + 1; + a_rReturn.reserve(cbFormatted); /* throws */ + int vrc = RTStrPrintHexBytes(a_rReturn.mutableRaw(), cbFormatted, pvData, cbData, RTSTRPRINTHEXBYTES_F_SEP_COLON); + a_rReturn.jolt(); + AssertRCReturn(vrc, Global::vboxStatusCodeToCOM(vrc)); + } + else + Assert(a_rReturn.isEmpty()); + return S_OK; +} + +/** + * Translates any ASN.1 object into a (DER encoded) byte array. + * + * @returns S_OK. + * @param a_pAsn1Obj The ASN.1 object to get the DER encoded bytes for. + * @param a_rReturn The return byte vector. + * @throws std::bad_alloc + */ +HRESULT Certificate::i_getEncodedBytes(PRTASN1CORE a_pAsn1Obj, std::vector<BYTE> &a_rReturn) +{ + HRESULT hrc = S_OK; + Assert(a_rReturn.size() == 0); + if (RTAsn1Core_IsPresent(a_pAsn1Obj)) + { + uint32_t cbEncoded; + int vrc = RTAsn1EncodePrepare(a_pAsn1Obj, 0, &cbEncoded, NULL); + if (RT_SUCCESS(vrc)) + { + a_rReturn.resize(cbEncoded); + Assert(a_rReturn.size() == cbEncoded); + if (cbEncoded) + { + vrc = RTAsn1EncodeToBuffer(a_pAsn1Obj, 0, &a_rReturn.front(), a_rReturn.size(), NULL); + if (RT_FAILURE(vrc)) + hrc = setErrorVrc(vrc, tr("RTAsn1EncodeToBuffer failed with %Rrc"), vrc); + } + } + else + hrc = setErrorVrc(vrc, tr("RTAsn1EncodePrepare failed with %Rrc"), vrc); + } + return hrc; +} + +/** @} */ + |