diff options
Diffstat (limited to 'src/VBox/Runtime/common/crypto')
74 files changed, 20630 insertions, 0 deletions
diff --git a/src/VBox/Runtime/common/crypto/Makefile.kup b/src/VBox/Runtime/common/crypto/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Runtime/common/crypto/Makefile.kup diff --git a/src/VBox/Runtime/common/crypto/RTCrPkcs5Pbkdf2Hmac-openssl.cpp b/src/VBox/Runtime/common/crypto/RTCrPkcs5Pbkdf2Hmac-openssl.cpp new file mode 100644 index 00000000..00756e90 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/RTCrPkcs5Pbkdf2Hmac-openssl.cpp @@ -0,0 +1,86 @@ +/* $Id: RTCrPkcs5Pbkdf2Hmac-openssl.cpp $ */ +/** @file + * IPRT - Crypto - RTCrPkcs5Pbkdf2Hmac implementation using OpenSSL. + */ + +/* + * Copyright (C) 2018-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifdef IPRT_WITH_OPENSSL +# include "internal/iprt.h" +# include <iprt/crypto/misc.h> + +# include <iprt/err.h> +# include <iprt/rand.h> +# include <iprt/assert.h> + +# include "internal/iprt-openssl.h" +# include "internal/openssl-pre.h" +# include <openssl/evp.h> +# include "internal/openssl-post.h" + + +RTDECL(int) RTCrPkcs5Pbkdf2Hmac(void const *pvInput, size_t cbInput, void const *pvSalt, size_t cbSalt, uint32_t cIterations, + RTDIGESTTYPE enmDigestType, size_t cbKeyLen, void *pvOutput) +{ + const EVP_MD *pDigest; + switch (enmDigestType) + { + case RTDIGESTTYPE_SHA1: + pDigest = EVP_sha1(); + break; + case RTDIGESTTYPE_SHA256: + pDigest = EVP_sha256(); + break; + case RTDIGESTTYPE_SHA512: + pDigest = EVP_sha512(); + break; + default: + AssertFailedReturn(VERR_CR_DIGEST_NOT_SUPPORTED); + } + + /* Note! This requires OpenSSL 1.0.0 or higher. */ + Assert((size_t)(int)cbInput == cbInput); + Assert((size_t)(int)cbSalt == cbSalt); + Assert((size_t)(int)cbKeyLen == cbKeyLen); + int rcOssl = PKCS5_PBKDF2_HMAC((const char *)pvInput, (int)cbInput, (const unsigned char *)pvSalt, (int)cbSalt, + (int)cIterations, pDigest, (int)cbKeyLen, (unsigned char *)pvOutput); + if (rcOssl) + return VINF_SUCCESS; + return VERR_CR_PASSWORD_2_KEY_DERIVIATION_FAILED; +} + +#endif /* IPRT_WITH_OPENSSL */ + diff --git a/src/VBox/Runtime/common/crypto/RTCrRandBytes-openssl.cpp b/src/VBox/Runtime/common/crypto/RTCrRandBytes-openssl.cpp new file mode 100644 index 00000000..fba7b406 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/RTCrRandBytes-openssl.cpp @@ -0,0 +1,69 @@ +/* $Id: RTCrRandBytes-openssl.cpp $ */ +/** @file + * IPRT - Crypto - RTCrRandBytes implementation using OpenSSL. + */ + +/* + * Copyright (C) 2018-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifdef IPRT_WITH_OPENSSL +# include "internal/iprt.h" +# include <iprt/crypto/misc.h> + +# include <iprt/rand.h> +# include <iprt/assert.h> +# include <iprt/err.h> + +# include "internal/iprt-openssl.h" +# include "internal/openssl-pre.h" +# include <openssl/rand.h> +# include "internal/openssl-post.h" + + +RTDECL(int) RTCrRandBytes(void *pvDst, size_t cbDst) +{ + /* Make sure the return buffer is always fully initialized in case the caller + doesn't properly check the return value. */ + RTRandBytes(pvDst, cbDst); /* */ + + /* Get cryptographically strong random. */ + rtCrOpenSslInit(); + Assert((size_t)(int)cbDst == cbDst); + int rcOpenSsl = RAND_bytes((uint8_t *)pvDst, (int)cbDst); + return rcOpenSsl > 0 ? VINF_SUCCESS : rcOpenSsl == 0 ? VERR_CR_RANDOM_FAILED : VERR_CR_RANDOM_SETUP_FAILED; +} + +#endif /* IPRT_WITH_OPENSSL */ + diff --git a/src/VBox/Runtime/common/crypto/RTCrStoreCertAddFromJavaKeyStore.cpp b/src/VBox/Runtime/common/crypto/RTCrStoreCertAddFromJavaKeyStore.cpp new file mode 100644 index 00000000..bf1f529d --- /dev/null +++ b/src/VBox/Runtime/common/crypto/RTCrStoreCertAddFromJavaKeyStore.cpp @@ -0,0 +1,320 @@ +/* $Id: RTCrStoreCertAddFromJavaKeyStore.cpp $ */ +/** @file + * IPRT - Cryptographic (Certificate) Store, RTCrStoreCertAddFromJavaKeyStore. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_CRYPTO +#include "internal/iprt.h" +#include <iprt/crypto/store.h> + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/sha.h> +#include <iprt/string.h> +#include <iprt/log.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The java key store magic number (file endian). */ +#define JKS_MAGIC RT_H2BE_U32_C(UINT32_C(0xfeedfeed)) +/** Java key store format version 2 (file endian). */ +#define JKS_VERSION_2 RT_H2BE_U32_C(2) + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Java key store (JKS) header. + */ +typedef struct JKSHEADER +{ + /** The magic (big endian) - JKS_MAGIC. */ + uint32_t uMagic; + /** Format version number (big endian) - JKS_VERSION_2. */ + uint32_t uVersion; + /** The number of keystore entries (big endian). */ + uint32_t cEntries; +} JKSHEADER; +/** Pointer to a const java key store header. */ +typedef JKSHEADER const *PCJKSHEADER; + + +RTDECL(int) RTCrStoreCertAddFromJavaKeyStoreInMem(RTCRSTORE hStore, uint32_t fFlags, void const *pvContent, size_t cbContent, + const char *pszErrorName, PRTERRINFO pErrInfo) +{ + uint8_t const *pbContent = (uint8_t const *)pvContent; + + /* + * Check the header. + */ + if (cbContent < sizeof(JKSHEADER) + RTSHA1_HASH_SIZE) + return RTErrInfoAddF(pErrInfo, VERR_WRONG_TYPE /** @todo better status codes */, + " Too small (%zu bytes) for java key store (%s)", cbContent, pszErrorName); + PCJKSHEADER pHdr = (PCJKSHEADER)pbContent; + if (pHdr->uMagic != JKS_MAGIC) + return RTErrInfoAddF(pErrInfo, VERR_WRONG_TYPE /** @todo better status codes */, + " Not java key store magic %#x (%s)", RT_BE2H_U32(pHdr->uMagic), pszErrorName); + if (pHdr->uVersion != JKS_VERSION_2) + return RTErrInfoAddF(pErrInfo, VERR_WRONG_TYPE /** @todo better status codes */, + " Unsupported java key store version %#x (%s)", RT_BE2H_U32(pHdr->uVersion), pszErrorName); + uint32_t const cEntries = RT_BE2H_U32(pHdr->cEntries); + if (cEntries > cbContent / 24) /* 24 = 4 for type, 4+ alias, 8 byte timestamp, 4 byte len, "X.509" or 4 cert count */ + return RTErrInfoAddF(pErrInfo, VERR_WRONG_TYPE /** @todo better status codes */, + " Entry count %u is to high for %zu byte JKS (%s)", cEntries, cbContent, pszErrorName); + + /* + * Here we should check the store signature. However, it always includes + * some kind of password, and that's somewhere we don't want to go right + * now. Later perhaps. + * + * We subtract it from the content size to make EOF checks simpler. + */ + int rc = VINF_SUCCESS; +#if 0 /* later */ + RTSHA1CONTEXT Ctx; + RTSha1Init(&Ctx); + + const char *pszCur = pszPassword; + for (;;) + { + RTUNICP Cp; + rc = RTStrGetCpEx(&pszCur, &Cp); + AssertRCReturn(rc, rc); + if (!Cp) + break; + uint8_t abWChar[2]; + abWChar[0] = RT_BYTE2(Cp); + abWChar[1] = RT_BYTE1(Cp); + RTSha1Update(&Ctx, &abWChar, sizeof(abWChar)); + } + + RTSha1Update(&Ctx, RT_STR_TUPLE("Mighty Aphrodite")); + + RTSha1Update(&Ctx, pbContent, cbContent - RTSHA1_HASH_SIZE); + + uint8_t abSignature[RTSHA1_HASH_SIZE]; + RTSha1Final(&Ctx, abSignature); + + if (memcmp(&pbContent[cbContent - RTSHA1_HASH_SIZE], abSignature, RTSHA1_HASH_SIZE) != 0) + { + rc = RTErrInfoAddF(pErrInfo, VERR_MISMATCH, " File SHA-1 signature mismatch, %.*Rhxs instead of %.*Rhxs, for '%s'", + RTSHA1_HASH_SIZE, abSignature, + RTSHA1_HASH_SIZE, &pbContent[cbContent - RTSHA1_HASH_SIZE], + pszErrorName); + if (!(fFlags & RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR)) + return rc; + } +#endif + cbContent -= RTSHA1_HASH_SIZE; + + + /* + * A bunch of macros to make decoding easier. + */ +#define ENSURE_CONTENT_OR_BREAK_EX(a_cbNeeded, a_pszWhat) \ + do { \ + if (RT_LIKELY(off + (a_cbNeeded) <= cbContent)) \ + { /* likely */ } \ + else \ + { \ + rc = RTErrInfoAddF(pErrInfo, VERR_EOF, " Unexpected end of data at %#x need %u bytes for %s (entry #%u in %s)", \ + off, a_cbNeeded, a_pszWhat, iEntry, pszErrorName); \ + break; \ + } \ + } while (0) +#define ENSURE_CONTENT_OR_BREAK(a_Var) ENSURE_CONTENT_OR_BREAK_EX(sizeof(a_Var), #a_Var) +#define GET_BE_U32_OR_BREAK(a_uVar) \ + do { \ + ENSURE_CONTENT_OR_BREAK(a_uVar); \ + AssertCompile(sizeof(a_uVar) == sizeof(uint32_t)); \ + a_uVar = RT_MAKE_U32_FROM_U8(pbContent[off + 3], pbContent[off + 2], pbContent[off + 1], pbContent[off + 0]); \ + off += sizeof(uint32_t); \ + } while (0) +#define GET_BE_U16_OR_BREAK(a_uVar) \ + do { \ + ENSURE_CONTENT_OR_BREAK(a_uVar); \ + AssertCompile(sizeof(a_uVar) == sizeof(uint16_t)); \ + a_uVar = RT_MAKE_U16(pbContent[off + 1], pbContent[off + 0]); \ + off += sizeof(uint16_t); \ + } while (0) +#define SKIP_CONTENT_BYTES_OR_BREAK(a_cbToSkip, a_pszWhat) \ + do { \ + ENSURE_CONTENT_OR_BREAK_EX(a_cbToSkip, a_pszWhat); \ + off += a_cbToSkip; \ + } while (0) +#define CHECK_OR_BREAK(a_Expr, a_RTErrInfoAddFArgs) \ + do { \ + if (RT_LIKELY(a_Expr)) \ + { /* likely */ } \ + else \ + { \ + rc = RTErrInfoAddF a_RTErrInfoAddFArgs; \ + break; \ + } \ + } while (0) + + /* + * Work our way thru the keystore. + */ + Log(("JKS: %u entries - '%s'\n", cEntries, pszErrorName)); + size_t off = sizeof(JKSHEADER); + uint32_t iEntry = 0; + for (;;) + { + size_t const offEntry = off; NOREF(offEntry); + + /* The entry type. */ + uint32_t uType; + GET_BE_U32_OR_BREAK(uType); + CHECK_OR_BREAK(uType == 1 || uType == 2, + (pErrInfo, VERR_WRONG_TYPE, " uType=%#x (entry #%u in %s)", uType, iEntry, pszErrorName)); + + /* Skip the alias string. */ + uint16_t cbAlias; + GET_BE_U16_OR_BREAK(cbAlias); + SKIP_CONTENT_BYTES_OR_BREAK(cbAlias, "szAlias"); + + /* Skip the creation timestamp. */ + SKIP_CONTENT_BYTES_OR_BREAK(sizeof(uint64_t), "tsCreated"); + + uint32_t cTrustCerts = 0; + if (uType == 1) + { + /* + * It is a private key. + */ + Log(("JKS: %#08zx: entry #%u: Private key\n", offEntry, iEntry)); + + /* The encoded key. */ + uint32_t cbKey; + GET_BE_U32_OR_BREAK(cbKey); + SKIP_CONTENT_BYTES_OR_BREAK(cbKey, "key data"); + + /* The number of trust certificates following it. */ + GET_BE_U32_OR_BREAK(cTrustCerts); + } + else if (uType == 2) + { + /* + * It is a certificate. + */ + Log(("JKS: %#08zx: entry #%u: Trust certificate\n", offEntry, iEntry)); + cTrustCerts = 1; + } + else + AssertFailedBreakStmt(rc = VERR_INTERNAL_ERROR_2); + + /* + * Decode trust certificates. Keys have 0 or more of these associated with them. + */ + for (uint32_t iCert = 0; iCert < cTrustCerts; iCert++) + { + /* X.509 signature */ + static const char a_achCertType[] = { 0, 5, 'X', '.', '5', '0', '9' }; + ENSURE_CONTENT_OR_BREAK(a_achCertType); + CHECK_OR_BREAK(memcmp(&pbContent[off], a_achCertType, sizeof(a_achCertType)) == 0, + (pErrInfo, VERR_WRONG_TYPE, " Unsupported certificate type %.7Rhxs (entry #%u in %s)", + &pbContent[off], iEntry, pszErrorName)); + off += sizeof(a_achCertType); + + /* The encoded certificate length. */ + uint32_t cbEncoded; + GET_BE_U32_OR_BREAK(cbEncoded); + ENSURE_CONTENT_OR_BREAK_EX(cbEncoded, "certificate data"); + Log(("JKS: %#08zx: %#x certificate bytes\n", off, cbEncoded)); + + /* Try add the certificate. */ + RTERRINFOSTATIC StaticErrInfo; + int rc2 = RTCrStoreCertAddEncoded(hStore, + RTCRCERTCTX_F_ENC_X509_DER | (fFlags & RTCRCERTCTX_F_ADD_IF_NOT_FOUND), + &pbContent[off], cbEncoded, RTErrInfoInitStatic(&StaticErrInfo)); + if (RT_FAILURE(rc2)) + { + if (RTErrInfoIsSet(&StaticErrInfo.Core)) + rc = RTErrInfoAddF(pErrInfo, rc2, " entry #%u: %s", iEntry, StaticErrInfo.Core.pszMsg); + else + rc = RTErrInfoAddF(pErrInfo, rc2, " entry #%u: %Rrc adding cert", iEntry, rc2); + if (!(fFlags & RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR)) + break; + } + off += cbEncoded; + } + + /* + * Advance. + */ + iEntry++; + if (iEntry >= cEntries) + { + if (off != cbContent) + rc = RTErrInfoAddF(pErrInfo, VERR_TOO_MUCH_DATA, " %zu tailing bytes (%s)", cbContent - off, pszErrorName); + break; + } + } + + return rc; +} + + +RTDECL(int) RTCrStoreCertAddFromJavaKeyStore(RTCRSTORE hStore, uint32_t fFlags, const char *pszFilename, PRTERRINFO pErrInfo) +{ + AssertReturn(!(fFlags & ~(RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR)), VERR_INVALID_FLAGS); + + /* + * Read the whole thing into memory as that's much more convenient to work + * with and we don't expect a java key store to take up a lot of space. + */ + size_t cbContent; + void *pvContent; + int rc = RTFileReadAllEx(pszFilename, 0, 32U*_1M, RTFILE_RDALL_O_DENY_WRITE, &pvContent, &cbContent); + if (RT_SUCCESS(rc)) + { + rc = RTCrStoreCertAddFromJavaKeyStoreInMem(hStore, fFlags, pvContent, cbContent, pszFilename, pErrInfo); + RTFileReadAllFree(pvContent, cbContent); + } + else + rc = RTErrInfoSetF(pErrInfo, rc, "RTFileReadAllEx failed with %Rrc on '%s'", rc, pszFilename); + return rc; +} +RT_EXPORT_SYMBOL(RTCrStoreCertAddFromJavaKeyStore); + diff --git a/src/VBox/Runtime/common/crypto/RTCrStoreCertAddWantedFromFishingExpedition.cpp b/src/VBox/Runtime/common/crypto/RTCrStoreCertAddWantedFromFishingExpedition.cpp new file mode 100644 index 00000000..b0ba5ad0 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/RTCrStoreCertAddWantedFromFishingExpedition.cpp @@ -0,0 +1,256 @@ +/* $Id: RTCrStoreCertAddWantedFromFishingExpedition.cpp $ */ +/** @file + * IPRT - Cryptographic (Certificate) Store, RTCrStoreCertAddFromFishingExpedition. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/store.h> + +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/path.h> + +#include "x509-internal.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#ifdef RT_OS_WINDOWS +# define PREFIX_UNIXROOT "${SystemDrive}/cygwin" +#elif defined(RT_OS_OS2) +# define PREFIX_UNIXROOT "/@unixroot@" +#else +# define PREFIX_UNIXROOT +#endif + + +/** + * Count the number of found certificates. + * + * @returns Number found. + * @param afFound Indicator array. + * @param cWanted Number of wanted certificates. + */ +DECLINLINE(size_t) rtCrStoreCountFound(bool const *afFound, size_t cWanted) +{ + size_t cFound = 0; + while (cWanted-- > 0) + if (afFound[cWanted]) + cFound++; + return cFound; +} + + +RTDECL(int) RTCrStoreCertAddWantedFromFishingExpedition(RTCRSTORE hStore, uint32_t fFlags, + PCRTCRCERTWANTED paWanted, size_t cWanted, + bool *pafFound, PRTERRINFO pErrInfo) +{ + int rc = VINF_SUCCESS; + int rc2; + + /* + * Validate input. + */ + AssertReturn(!(fFlags & ~(RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR)), VERR_INVALID_FLAGS); + fFlags |= RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR; /* force these! */ + AssertReturn(cWanted, VERR_NOT_FOUND); + for (uint32_t i = 0; i < cWanted; i++) + { + AssertReturn(!paWanted[i].pszSubject || *paWanted[i].pszSubject, VERR_INVALID_PARAMETER); + AssertReturn( paWanted[i].pszSubject + || paWanted[i].fSha1Fingerprint + || paWanted[i].fSha512Fingerprint, + VERR_INVALID_PARAMETER); + } + + /* + * Make sure we've got a result array. + */ + bool *pafFoundFree = NULL; + if (!pafFound) + { + pafFound = pafFoundFree = (bool *)RTMemTmpAllocZ(sizeof(bool) * cWanted); + AssertReturn(pafFound, VERR_NO_TMP_MEMORY); + } + + /* + * Search the user and system stores first. + */ + bool fAllFound = false; + RTCRSTORE hTmpStore; + for (int iStoreId = RTCRSTOREID_INVALID + 1; iStoreId < RTCRSTOREID_END; iStoreId++) + { + rc2 = RTCrStoreCreateSnapshotById(&hTmpStore, (RTCRSTOREID)iStoreId, NULL); + if (RT_SUCCESS(rc2)) + { + rc2 = RTCrStoreCertAddWantedFromStore(hStore, fFlags, hTmpStore, paWanted, cWanted, pafFound); + RTCrStoreRelease(hTmpStore); + fAllFound = rc2 == VINF_SUCCESS; + if (fAllFound) + break; + } + } + + /* + * Search alternative file based stores. + */ + if (!fAllFound) + { + static const char * const s_apszFiles[] = + { + PREFIX_UNIXROOT "/usr/share/ca-certificates/trust-source/mozilla.neutral-trust.crt", + PREFIX_UNIXROOT "/usr/share/ca-certificates/trust-source/mozilla.trust.crt", + PREFIX_UNIXROOT "/usr/share/doc/mutt/samples/ca-bundle.crt", + PREFIX_UNIXROOT "/usr/jdk/latest/jre/lib/security/cacerts", + PREFIX_UNIXROOT "/usr/share/curl/curl-ca-bundle.crt", +#ifdef RT_OS_DARWIN + "/opt/local/share/curl/curl-ca-bundle.crt", + "/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/lib/security/cacerts", + "/System/Library/Java/Support/CoreDeploy.bundle/Contents/Home/lib/security/cacerts", + "/System/Library/Java/Support/CoreDeploy.bundle/Contents/JavaAppletPlugin.plugin/Contents/Home/lib/security/cacerts", + "/System/Library/Java/Support/Deploy.bundle/Contents/Home/lib/security/cacerts", + "/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/MacOS/itms/java/lib/security/cacerts", + "/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/itms/java/lib/security/cacerts", + "/Applications/Xcode-beta.app/Contents/Applications/Application Loader.app/Contents/itms/java/lib/security/cacerts", + "/System/Library/Java/JavaVirtualMachines/*/Contents/Home/lib/security/cacerts", +#endif +#ifdef RT_OS_LINUX + PREFIX_UNIXROOT "/etc/ssl/certs/java/cacerts", + PREFIX_UNIXROOT "/usr/lib/j*/*/jre/lib/security/cacerts", + PREFIX_UNIXROOT "/opt/*/jre/lib/security/cacerts", +#endif +#ifdef RT_OS_SOLARIS + PREFIX_UNIXROOT "/usr/java/jre/lib/security/cacerts", + PREFIX_UNIXROOT "/usr/jdk/instances/*/jre/lib/security/cacerts", +#endif +#ifdef RT_OS_WINDOWS + "${AllProgramFiles}/Git/bin/curl-ca-bundle.crt", + "${AllProgramFiles}/Mercurial/hgrc.d/cacert.pem", + "${AllProgramFiles}/Java/jre*/lib/security/cacerts", + "${AllProgramFiles}/Java/jdk*/jre/lib/security/cacerts", + "${AllProgramFiles}/HexChat/cert.pem", + "${SystemDrive}/BitNami/*/git/bin/curl-ca-bundle.crt", + "${SystemDrive}/BitNami/*/heroku/data/cacert.pem", + "${SystemDrive}/BitNami/*/heroku/vendor/gems/excon*/data/cacert.pem", + "${SystemDrive}/BitNami/*/php/PEAR/AWSSDKforPHP/lib/requstcore/cacert.pem", +#endif + }; + for (uint32_t i = 0; i < RT_ELEMENTS(s_apszFiles) && !fAllFound; i++) + { + PCRTPATHGLOBENTRY pResultHead; + rc2 = RTPathGlob(s_apszFiles[i], RTPATHGLOB_F_NO_DIRS, &pResultHead, NULL); + if (RT_SUCCESS(rc2)) + { + for (PCRTPATHGLOBENTRY pCur = pResultHead; pCur; pCur = pCur->pNext) + { + rc2 = RTCrStoreCertAddWantedFromFile(hStore, fFlags, pCur->szPath, paWanted, cWanted, pafFound, pErrInfo); + fAllFound = rc2 == VINF_SUCCESS; + if (fAllFound) + break; + } + RTPathGlobFree(pResultHead); + } + } + } + + /* + * Search alternative directory based stores. + */ + if (!fAllFound) + { + static const char * const s_apszFiles[] = + { + PREFIX_UNIXROOT "/usr/share/ca-certificates/mozilla/", +#ifdef RT_OS_DARWIN + "/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/rubygems/ssl_certs/", +#endif +#ifdef RT_OS_SOLARIS + "/etc/certs/", + "/etc/crypto/certs/", +#endif +#ifdef RT_OS_WINDOWS + "${AllProgramFiles}/Git/ssl/certs/", + "${AllProgramFiles}/Git/ssl/certs/expired/", + "${AllProgramFiles}/Common Files/Apple/Internet Services/security.resources/roots/", + "${AllProgramFiles}/Raptr/ca-certs/", + "${SystemDrive}/Bitname/*/git/ssl/certs/", + "${SystemDrive}/Bitnami/*/git/ssl/certs/expired/", +#endif + }; + for (uint32_t i = 0; i < RT_ELEMENTS(s_apszFiles) && !fAllFound; i++) + { + PCRTPATHGLOBENTRY pResultHead; + rc2 = RTPathGlob(s_apszFiles[i], RTPATHGLOB_F_ONLY_DIRS, &pResultHead, NULL); + if (RT_SUCCESS(rc2)) + { + for (PCRTPATHGLOBENTRY pCur = pResultHead; pCur; pCur = pCur->pNext) + { + rc2 = RTCrStoreCertAddWantedFromDir(hStore, fFlags, pCur->szPath, NULL /*paSuffixes*/, 0 /*cSuffixes*/, + paWanted, cWanted, pafFound, pErrInfo); + fAllFound = rc2 == VINF_SUCCESS; + if (fAllFound) + break; + } + RTPathGlobFree(pResultHead); + } + } + } + + /* + * If all found, return VINF_SUCCESS, otherwise warn that we didn't find everything. + */ + if (RT_SUCCESS(rc)) + { + size_t cFound = rtCrStoreCountFound(pafFound, cWanted); + Assert(cFound == cWanted || !fAllFound); + if (cFound == cWanted) + rc = VINF_SUCCESS; + else if (cFound > 0) + rc = VWRN_NOT_FOUND; + else + rc = VERR_NOT_FOUND; + } + + if (pafFoundFree) + RTMemTmpFree(pafFoundFree); + return rc; +} +RT_EXPORT_SYMBOL(RTCrStoreCertAddWantedFromFishingExpedition); + diff --git a/src/VBox/Runtime/common/crypto/RTCrStoreCertExportAsPem.cpp b/src/VBox/Runtime/common/crypto/RTCrStoreCertExportAsPem.cpp new file mode 100644 index 00000000..24232165 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/RTCrStoreCertExportAsPem.cpp @@ -0,0 +1,147 @@ +/* $Id: RTCrStoreCertExportAsPem.cpp $ */ +/** @file + * IPRT - Cryptographic (Certificate) Store, RTCrStoreCertExportAsPem. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/store.h> + +#include <iprt/assert.h> +#include <iprt/base64.h> +#include <iprt/dir.h> +#include <iprt/errcore.h> +#include <iprt/mem.h> +#include <iprt/stream.h> + + + +RTDECL(int) RTCrStoreCertExportAsPem(RTCRSTORE hStore, uint32_t fFlags, const char *pszFilename) +{ + /* + * Validate input. + */ + AssertReturn(!fFlags, VERR_INVALID_FLAGS); + + /* + * Start the enumeration first as this validates the store handle. + */ + RTCRSTORECERTSEARCH Search; + int rc = RTCrStoreCertFindAll(hStore, &Search); + if (RT_SUCCESS(rc)) + { + /* + * Open the file for writing. + * + * Note! We must use text and no binary here, because the base-64 API + * below will use host specific EOL markers, not CRLF as PEM + * specifies. + */ + PRTSTREAM hStrm; + rc = RTStrmOpen(pszFilename, "w", &hStrm); + if (RT_SUCCESS(rc)) + { + /* + * Enumerate the certificates in the store, writing them out one by one. + */ + size_t cbBase64 = 0; + char *pszBase64 = NULL; + PCRTCRCERTCTX pCertCtx; + while ((pCertCtx = RTCrStoreCertSearchNext(hStore, &Search)) != NULL) + { + const char *pszMarker; + switch (pCertCtx->fFlags & RTCRCERTCTX_F_ENC_MASK) + { + case RTCRCERTCTX_F_ENC_X509_DER: pszMarker = "CERTIFICATE"; break; + case RTCRCERTCTX_F_ENC_TAF_DER: pszMarker = "TRUST ANCHOR"; break; + default: pszMarker = NULL; break; + } + if (pszMarker && pCertCtx->cbEncoded > 0) + { + /* + * Do the base64 conversion first. + */ + size_t cchEncoded = RTBase64EncodedLength(pCertCtx->cbEncoded); + if (cchEncoded < cbBase64) + { /* likely */ } + else + { + size_t cbNew = RT_ALIGN(cchEncoded + 64, 128); + void *pvNew = RTMemRealloc(pszBase64, cbNew); + if (!pvNew) + { + rc = VERR_NO_MEMORY; + break; + } + cbBase64 = cbNew; + pszBase64 = (char *)pvNew; + } + rc = RTBase64Encode(pCertCtx->pabEncoded, pCertCtx->cbEncoded, pszBase64, cbBase64, &cchEncoded); + if (RT_FAILURE(rc)) + break; + + RTStrmPrintf(hStrm, "-----BEGIN %s-----\n", pszMarker); + RTStrmWrite(hStrm, pszBase64, cchEncoded); + rc = RTStrmPrintf(hStrm, "\n-----END %s-----\n", pszMarker); + if (RT_FAILURE(rc)) + break; + } + + RTCrCertCtxRelease(pCertCtx); + } + if (pCertCtx) + RTCrCertCtxRelease(pCertCtx); + RTMemFree(pszBase64); + + /* + * Flush the output file before closing. + */ + int rc2 = RTStrmFlush(hStrm); + if (RT_FAILURE(rc2) && RT_SUCCESS(rc)) + rc = rc2; + RTStrmClearError(hStrm); /** @todo fix RTStrmClose... */ + rc2 = RTStrmClose(hStrm); + if (RT_FAILURE(rc2) && RT_SUCCESS(rc)) + rc = rc2; + } + + int rc2 = RTCrStoreCertSearchDestroy(hStore, &Search); AssertRC(rc2); + } + return rc; +} +RT_EXPORT_SYMBOL(RTCrStoreCertExportAsPem); + diff --git a/src/VBox/Runtime/common/crypto/RTCrStoreCreateSnapshotOfUserAndSystemTrustedCAsAndCerts.cpp b/src/VBox/Runtime/common/crypto/RTCrStoreCreateSnapshotOfUserAndSystemTrustedCAsAndCerts.cpp new file mode 100644 index 00000000..44dc35f9 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/RTCrStoreCreateSnapshotOfUserAndSystemTrustedCAsAndCerts.cpp @@ -0,0 +1,89 @@ +/* $Id: RTCrStoreCreateSnapshotOfUserAndSystemTrustedCAsAndCerts.cpp $ */ +/** @file + * IPRT - Cryptographic (Certificate) Store, RTCrStoreCreateSnapshotOfUserAndSystemTrustedCAsAndCerts. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/store.h> + +#include <iprt/errcore.h> + + + + +RTR3DECL(int) RTCrStoreCreateSnapshotOfUserAndSystemTrustedCAsAndCerts(PRTCRSTORE phStore, PRTERRINFO pErrInfo) +{ + /* + * Start by a snapshot of the user store. + */ + RTCRSTORE hDstStore; + int rc = RTCrStoreCreateSnapshotById(&hDstStore, RTCRSTOREID_USER_TRUSTED_CAS_AND_CERTIFICATES, pErrInfo); + if (RT_SUCCESS(rc)) + { + /* + * Snapshot the system store and add that to the user snapshot. Ignore errors. + */ + RTCRSTORE hSrcStore; + rc = RTCrStoreCreateSnapshotById(&hSrcStore, RTCRSTOREID_SYSTEM_TRUSTED_CAS_AND_CERTIFICATES, pErrInfo); + if (RT_SUCCESS(rc)) + { + rc = RTCrStoreCertAddFromStore(hDstStore, RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR, + hSrcStore); + RTCrStoreRelease(hSrcStore); + } + if (RT_SUCCESS(rc)) + { + *phStore = hDstStore; + return rc; + } + + /* + * If we've got something in the store, return anyway despite of the error condition. + */ + if (rc != VERR_NO_MEMORY && RTCrStoreCertCount(hDstStore) > 0) + { + *phStore = hDstStore; + return -rc; + } + + RTCrStoreRelease(hDstStore); + } + *phStore = NIL_RTCRSTORE; + return rc; +} + diff --git a/src/VBox/Runtime/common/crypto/cipher-openssl.cpp b/src/VBox/Runtime/common/crypto/cipher-openssl.cpp new file mode 100644 index 00000000..4d0cdc85 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/cipher-openssl.cpp @@ -0,0 +1,596 @@ +/* $Id: cipher-openssl.cpp $ */ +/** @file + * IPRT - Crypto - Symmetric Cipher using OpenSSL. + */ + +/* + * Copyright (C) 2018-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifdef IPRT_WITH_OPENSSL +# include "internal/iprt.h" +# include <iprt/crypto/cipher.h> + +# include <iprt/asm.h> +# include <iprt/assert.h> +# include <iprt/err.h> +# include <iprt/mem.h> +# include <iprt/string.h> + +# include "internal/iprt-openssl.h" +# include "internal/openssl-pre.h" +# include <openssl/evp.h> +# include "internal/openssl-post.h" + +# include "internal/magics.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#if defined(EVP_CTRL_AEAD_GET_TAG) +# define MY_EVP_CTRL_AEAD_GET_TAG EVP_CTRL_AEAD_GET_TAG +#else +# define MY_EVP_CTRL_AEAD_GET_TAG EVP_CTRL_GCM_GET_TAG +#endif + +#if defined(EVP_CTRL_AEAD_SET_TAG) +# define MY_EVP_CTRL_AEAD_SET_TAG EVP_CTRL_AEAD_SET_TAG +#else +# define MY_EVP_CTRL_AEAD_SET_TAG EVP_CTRL_GCM_SET_TAG +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * OpenSSL cipher instance data. + */ +typedef struct RTCRCIPHERINT +{ + /** Magic value (RTCRCIPHERINT_MAGIC). */ + uint32_t u32Magic; + /** Reference count. */ + uint32_t volatile cRefs; + /** The cihper. */ + const EVP_CIPHER *pCipher; + /** The IPRT cipher type, if we know it. */ + RTCRCIPHERTYPE enmType; +} RTCRCIPHERINT; + + +/** + * OpenSSL cipher context data + */ +typedef struct RTCRCIPHERCTXINT +{ + /** Pointer to cipher instance data */ + RTCRCIPHERINT *phCipher; + /** Pointer to cipher context */ + EVP_CIPHER_CTX *pCipherCtx; + /** Is decryption */ + bool fDecryption; +} RTCRCIPHERCTXINT; + + +RTDECL(int) RTCrCipherOpenByType(PRTCRCIPHER phCipher, RTCRCIPHERTYPE enmType, uint32_t fFlags) +{ + AssertPtrReturn(phCipher, VERR_INVALID_POINTER); + *phCipher = NIL_RTCRCIPHER; + AssertReturn(!fFlags, VERR_INVALID_FLAGS); + + /* + * Translate the IPRT cipher type to EVP cipher. + */ + const EVP_CIPHER *pCipher = NULL; + switch (enmType) + { + case RTCRCIPHERTYPE_XTS_AES_128: + pCipher = EVP_aes_128_xts(); + break; + case RTCRCIPHERTYPE_XTS_AES_256: + pCipher = EVP_aes_256_xts(); + break; + case RTCRCIPHERTYPE_GCM_AES_128: + pCipher = EVP_aes_128_gcm(); + break; + case RTCRCIPHERTYPE_GCM_AES_256: + pCipher = EVP_aes_256_gcm(); + break; + case RTCRCIPHERTYPE_CTR_AES_128: + pCipher = EVP_aes_128_ctr(); + break; + case RTCRCIPHERTYPE_CTR_AES_256: + pCipher = EVP_aes_256_ctr(); + break; + + /* no default! */ + case RTCRCIPHERTYPE_INVALID: + case RTCRCIPHERTYPE_END: + case RTCRCIPHERTYPE_32BIT_HACK: + AssertFailedReturn(VERR_INVALID_PARAMETER); + } + AssertReturn(pCipher, VERR_CR_CIPHER_NOT_SUPPORTED); + + /* + * Create the instance. + */ + RTCRCIPHERINT *pThis = (RTCRCIPHERINT *)RTMemAllocZ(sizeof(*pThis)); + if (pThis) + { + pThis->u32Magic = RTCRCIPHERINT_MAGIC; + pThis->cRefs = 1; + pThis->pCipher = pCipher; + pThis->enmType = enmType; + *phCipher = pThis; + return VINF_SUCCESS; + } + return VERR_NO_MEMORY; +} + + +RTDECL(uint32_t) RTCrCipherRetain(RTCRCIPHER hCipher) +{ + RTCRCIPHERINT *pThis = hCipher; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTCRCIPHERINT_MAGIC, UINT32_MAX); + + uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs); + Assert(cRefs > 1 && cRefs < 1024); + return cRefs; +} + + +/** + * Destroys the cipher instance. + */ +static uint32_t rtCrCipherDestroy(RTCRCIPHER pThis) +{ + pThis->u32Magic= ~RTCRCIPHERINT_MAGIC; + pThis->pCipher = NULL; + RTMemFree(pThis); + return 0; +} + + +RTDECL(uint32_t) RTCrCipherRelease(RTCRCIPHER hCipher) +{ + RTCRCIPHERINT *pThis = hCipher; + if (pThis == NIL_RTCRCIPHER) + return 0; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTCRCIPHERINT_MAGIC, UINT32_MAX); + + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + Assert(cRefs < 1024); + if (cRefs == 0) + return rtCrCipherDestroy(pThis); + return cRefs; +} + + +RTDECL(uint32_t) RTCrCipherGetKeyLength(RTCRCIPHER hCipher) +{ + RTCRCIPHERINT *pThis = hCipher; + AssertPtrReturn(pThis, 0); + AssertReturn(pThis->u32Magic == RTCRCIPHERINT_MAGIC, 0); + + return EVP_CIPHER_key_length(pThis->pCipher); +} + + +RTDECL(uint32_t) RTCrCipherGetInitializationVectorLength(RTCRCIPHER hCipher) +{ + RTCRCIPHERINT *pThis = hCipher; + AssertPtrReturn(pThis, 0); + AssertReturn(pThis->u32Magic == RTCRCIPHERINT_MAGIC, 0); + + return EVP_CIPHER_iv_length(pThis->pCipher); +} + + +RTDECL(uint32_t) RTCrCipherGetBlockSize(RTCRCIPHER hCipher) +{ + RTCRCIPHERINT *pThis = hCipher; + AssertPtrReturn(pThis, 0); + AssertReturn(pThis->u32Magic == RTCRCIPHERINT_MAGIC, 0); + + return EVP_CIPHER_block_size(pThis->pCipher); +} + + +RTDECL(int) RTCrCipherCtxFree(RTCRCIPHERCTX hCipherCtx) +{ + AssertReturn(hCipherCtx, VERR_INVALID_PARAMETER); + RTCRCIPHERCTXINT *pCtx = hCipherCtx; + +# if OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(LIBRESSL_VERSION_NUMBER) + EVP_CIPHER_CTX_free(pCtx->pCipherCtx); +# else + EVP_CIPHER_CTX_cleanup(pCtx->pCipherCtx); + RTMemFree(pCtx->pCipherCtx); +# endif + RTMemFree(pCtx); + + return VINF_SUCCESS; +} + + +RTDECL(int) RTCrCipherCtxEncryptInit(RTCRCIPHER hCipher, void const *pvKey, size_t cbKey, + void const *pvInitVector, size_t cbInitVector, + void const *pvAuthData, size_t cbAuthData, + PRTCRCIPHERCTX phCipherCtx) +{ + /* + * Validate input. + */ + RTCRCIPHERINT *pThis = hCipher; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRCIPHERINT_MAGIC, VERR_INVALID_HANDLE); + AssertMsgReturn((ssize_t)cbKey == EVP_CIPHER_key_length(pThis->pCipher), + ("%zu, expected %d\n", cbKey, EVP_CIPHER_key_length(pThis->pCipher)), + VERR_CR_CIPHER_INVALID_KEY_LENGTH); + AssertMsgReturn((ssize_t)cbInitVector == EVP_CIPHER_iv_length(pThis->pCipher), + ("%zu, expected %d\n", cbInitVector, EVP_CIPHER_iv_length(pThis->pCipher)), + VERR_CR_CIPHER_INVALID_INITIALIZATION_VECTOR_LENGTH); + + Assert(EVP_CIPHER_block_size(pThis->pCipher) <= 1); /** @todo more complicated ciphers later */ + + /* + * Allocate and initialize the cipher context. + */ + int rc = VERR_NO_MEMORY; + /* + * Create the instance. + */ + RTCRCIPHERCTXINT *pCtx = (RTCRCIPHERCTXINT *)RTMemAlloc(sizeof(RTCRCIPHERCTXINT)); + if (pCtx) + { + pCtx->phCipher = hCipher; + pCtx->fDecryption = false; +# if OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(LIBRESSL_VERSION_NUMBER) + pCtx->pCipherCtx = EVP_CIPHER_CTX_new(); + if (pCtx->pCipherCtx) +# else + pCtx->pCipherCtx = (EVP_CIPHER_CTX *)RTMemAllocZ(sizeof(EVP_CIPHER_CTX)); +# endif + { + if (EVP_EncryptInit(pCtx->pCipherCtx, pCtx->phCipher->pCipher, (unsigned char const *)pvKey, + (unsigned char const *)pvInitVector)) + { + if (pvAuthData && cbAuthData) + { + /* Add auth data. */ + int cbEncryptedAuth = 0; + rc = EVP_EncryptUpdate(pCtx->pCipherCtx, NULL, &cbEncryptedAuth, + (unsigned char const *)pvAuthData, (int)cbAuthData) ? VINF_SUCCESS + : VERR_CR_CIPHER_OSSL_ENCRYPT_UPDATE_FAILED; + } + else + rc = VINF_SUCCESS; + } + else + rc = VERR_CR_CIPHER_OSSL_ENCRYPT_INIT_FAILED; + } + } + + if (RT_SUCCESS(rc)) + *phCipherCtx = pCtx; + else + RTCrCipherCtxFree(pCtx); + return rc; +} + + +RTDECL(int) RTCrCipherCtxEncryptProcess(RTCRCIPHERCTX hCipherCtx, void const *pvPlainText, size_t cbPlainText, + void *pvEncrypted, size_t cbEncrypted, size_t *pcbEncrypted) +{ + AssertReturn(hCipherCtx, VERR_INVALID_PARAMETER); + AssertReturn(cbPlainText > 0, VERR_NO_DATA); + AssertReturn((size_t)(int)cbPlainText == cbPlainText && (int)cbPlainText > 0, VERR_OUT_OF_RANGE); + AssertReturn(cbEncrypted >= cbPlainText, VERR_BUFFER_OVERFLOW); + + RTCRCIPHERCTXINT *pCtx = hCipherCtx; + AssertReturn(!pCtx->fDecryption, VERR_INVALID_STATE); + int cbEncrypted1 = 0; + int rc = VERR_CR_CIPHER_OSSL_ENCRYPT_UPDATE_FAILED; + if (EVP_EncryptUpdate(pCtx->pCipherCtx, (unsigned char *)pvEncrypted, &cbEncrypted1, + (unsigned char const *)pvPlainText, (int)cbPlainText)) + { + *pcbEncrypted = cbEncrypted1; + rc = VINF_SUCCESS; + } + return rc; +} + + +RTDECL(int) RTCrCipherCtxEncryptFinish(RTCRCIPHERCTX hCipherCtx, + void *pvEncrypted, size_t *pcbEncrypted, + void *pvTag, size_t cbTag, size_t *pcbTag) +{ + AssertReturn(hCipherCtx, VERR_INVALID_PARAMETER); + RTCRCIPHERCTXINT *pCtx = hCipherCtx; + AssertReturn(!pCtx->fDecryption, VERR_INVALID_STATE); + AssertReturn(!pvTag || (pvTag && cbTag == 16), VERR_CR_CIPHER_INVALID_TAG_LENGTH); + int cbEncrypted2 = 0; + int rc = VERR_CR_CIPHER_OSSL_ENCRYPT_FINAL_FAILED; + if (EVP_EncryptFinal(pCtx->pCipherCtx, (uint8_t *)pvEncrypted, &cbEncrypted2)) + { + if (pvTag && cbTag) + { + if (EVP_CIPHER_CTX_ctrl(pCtx->pCipherCtx, MY_EVP_CTRL_AEAD_GET_TAG, (int)cbTag, pvTag)) + { + *pcbTag = cbTag; + rc = VINF_SUCCESS; + } + else + rc = VERR_CR_CIPHER_OSSL_GET_TAG_FAILED; + } + else + rc = VINF_SUCCESS; + + if (RT_SUCCESS(rc) && pcbEncrypted) + *pcbEncrypted = cbEncrypted2; + } + + return rc; +} + + +RTDECL(int) RTCrCipherCtxDecryptInit(RTCRCIPHER hCipher, void const *pvKey, size_t cbKey, + void const *pvInitVector, size_t cbInitVector, + void const *pvAuthData, size_t cbAuthData, + void *pvTag, size_t cbTag, PRTCRCIPHERCTX phCipherCtx) +{ + /* + * Validate input. + */ + RTCRCIPHERINT *pThis = hCipher; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRCIPHERINT_MAGIC, VERR_INVALID_HANDLE); + AssertMsgReturn((ssize_t)cbKey == EVP_CIPHER_key_length(pThis->pCipher), + ("%zu, expected %d\n", cbKey, EVP_CIPHER_key_length(pThis->pCipher)), + VERR_CR_CIPHER_INVALID_KEY_LENGTH); + AssertMsgReturn((ssize_t)cbInitVector == EVP_CIPHER_iv_length(pThis->pCipher), + ("%zu, expected %d\n", cbInitVector, EVP_CIPHER_iv_length(pThis->pCipher)), + VERR_CR_CIPHER_INVALID_INITIALIZATION_VECTOR_LENGTH); + AssertReturn(!pvTag || (pvTag && cbTag == 16), VERR_CR_CIPHER_INVALID_TAG_LENGTH); + + Assert(EVP_CIPHER_block_size(pThis->pCipher) <= 1); /** @todo more complicated ciphers later */ + + /* + * Allocate and initialize the cipher context. + */ + int rc = VERR_NO_MEMORY; + /* + * Create the instance. + */ + RTCRCIPHERCTXINT *pCtx = (RTCRCIPHERCTXINT *)RTMemAlloc(sizeof(RTCRCIPHERCTXINT)); + if (pCtx) + { + pCtx->phCipher = hCipher; + pCtx->fDecryption = true; +# if OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(LIBRESSL_VERSION_NUMBER) + pCtx->pCipherCtx = EVP_CIPHER_CTX_new(); +# else + pCtx->pCipherCtx = (EVP_CIPHER_CTX *)RTMemAllocZ(sizeof(EVP_CIPHER_CTX)); +# endif + + if (EVP_DecryptInit(pCtx->pCipherCtx, pThis->pCipher, (unsigned char const *)pvKey, + (unsigned char const *)pvInitVector)) + { + rc = VINF_SUCCESS; + if (pvTag && cbTag && !EVP_CIPHER_CTX_ctrl(pCtx->pCipherCtx, MY_EVP_CTRL_AEAD_SET_TAG, (int)cbTag, pvTag)) + rc = VERR_CR_CIPHER_OSSL_SET_TAG_FAILED; + + if (RT_SUCCESS(rc) && pvAuthData && cbAuthData) + { + /* Add auth data. */ + int cbDecryptedAuth = 0; + if (!EVP_DecryptUpdate(pCtx->pCipherCtx, NULL, &cbDecryptedAuth, + (unsigned char const *)pvAuthData, (int)cbAuthData)) + rc = VERR_CR_CIPHER_OSSL_DECRYPT_UPDATE_FAILED; + } + } + else + rc = VERR_CR_CIPHER_OSSL_DECRYPT_INIT_FAILED; + } + + if (RT_SUCCESS(rc)) + *phCipherCtx = pCtx; + else + RTCrCipherCtxFree(pCtx); + + return rc; +} + + +RTDECL(int) RTCrCipherCtxDecryptProcess(RTCRCIPHERCTX hCipherCtx, + void const *pvEncrypted, size_t cbEncrypted, + void *pvPlainText, size_t cbPlainText, size_t *pcbPlainText) +{ + AssertReturn(hCipherCtx, VERR_INVALID_PARAMETER); + AssertReturn(cbEncrypted > 0, VERR_NO_DATA); + AssertReturn((size_t)(int)cbEncrypted == cbEncrypted && (int)cbEncrypted > 0, VERR_OUT_OF_RANGE); + AssertReturn(cbPlainText >= cbEncrypted, VERR_BUFFER_OVERFLOW); + + RTCRCIPHERCTXINT *pCtx = hCipherCtx; + AssertReturn(pCtx->fDecryption, VERR_INVALID_STATE); + int rc = VERR_CR_CIPHER_OSSL_DECRYPT_UPDATE_FAILED; + int cbDecrypted1 = 0; + if (EVP_DecryptUpdate(pCtx->pCipherCtx, (unsigned char *)pvPlainText, &cbDecrypted1, + (unsigned char const *)pvEncrypted, (int)cbEncrypted)) + { + *pcbPlainText = cbDecrypted1; + rc = VINF_SUCCESS; + } + return rc; +} + + +RTDECL(int) RTCrCipherCtxDecryptFinish(RTCRCIPHERCTX hCipherCtx, + void *pvPlainText, size_t *pcbPlainText) +{ + AssertReturn(hCipherCtx, VERR_INVALID_PARAMETER); + RTCRCIPHERCTXINT *pCtx = hCipherCtx; + AssertReturn(pCtx->fDecryption, VERR_INVALID_STATE); + int cbDecrypted2 = 0; + int rc = VERR_CR_CIPHER_OSSL_ENCRYPT_FINAL_FAILED; + if (EVP_DecryptFinal(pCtx->pCipherCtx, (uint8_t *)pvPlainText, &cbDecrypted2)) + { + rc = VINF_SUCCESS; + if (pcbPlainText) + *pcbPlainText = cbDecrypted2; + } + + return rc; +} + + +RTDECL(int) RTCrCipherEncrypt(RTCRCIPHER hCipher, void const *pvKey, size_t cbKey, + void const *pvInitVector, size_t cbInitVector, + void const *pvPlainText, size_t cbPlainText, + void *pvEncrypted, size_t cbEncrypted, size_t *pcbEncrypted) +{ + return RTCrCipherEncryptEx(hCipher, pvKey, cbKey, pvInitVector, cbInitVector, + NULL, 0, pvPlainText, cbPlainText, pvEncrypted, cbEncrypted, + pcbEncrypted, NULL, 0, NULL); +} + + +RTDECL(int) RTCrCipherDecrypt(RTCRCIPHER hCipher, void const *pvKey, size_t cbKey, + void const *pvInitVector, size_t cbInitVector, + void const *pvEncrypted, size_t cbEncrypted, + void *pvPlainText, size_t cbPlainText, size_t *pcbPlainText) +{ + return RTCrCipherDecryptEx(hCipher, pvKey, cbKey, pvInitVector, cbInitVector, + NULL, 0, NULL, 0, pvEncrypted, cbEncrypted, + pvPlainText, cbPlainText, pcbPlainText); +} + + +RTDECL(int) RTCrCipherEncryptEx(RTCRCIPHER hCipher, void const *pvKey, size_t cbKey, + void const *pvInitVector, size_t cbInitVector, + void const *pvAuthData, size_t cbAuthData, + void const *pvPlainText, size_t cbPlainText, + void *pvEncrypted, size_t cbEncrypted, size_t *pcbEncrypted, + void *pvTag, size_t cbTag, size_t *pcbTag) +{ + size_t const cbNeeded = cbPlainText; + if (pcbEncrypted) + { + *pcbEncrypted = cbNeeded; + AssertReturn(cbEncrypted >= cbNeeded, VERR_BUFFER_OVERFLOW); + } + else + AssertReturn(cbEncrypted == cbNeeded, VERR_INVALID_PARAMETER); + AssertReturn((size_t)(int)cbPlainText == cbPlainText && (int)cbPlainText > 0, VERR_OUT_OF_RANGE); + + RTCRCIPHERCTXINT *pCtx = NIL_RTCRCIPHERCTX; + + int rc = RTCrCipherCtxEncryptInit(hCipher, pvKey, cbKey, pvInitVector, cbInitVector, + pvAuthData, cbAuthData, &pCtx); + if (RT_SUCCESS(rc)) + { + size_t cbEncrypted1 = 0; + rc = RTCrCipherCtxEncryptProcess(pCtx, pvPlainText, cbPlainText, pvEncrypted, cbEncrypted, &cbEncrypted1); + if (RT_SUCCESS(rc)) + { + size_t cbEncrypted2 = 0; + rc = RTCrCipherCtxEncryptFinish(pCtx, (unsigned char *)pvEncrypted + cbEncrypted1, + &cbEncrypted2, pvTag, cbTag, pcbTag); + if (RT_SUCCESS(rc)) + { + Assert(cbEncrypted1 + cbEncrypted2 == cbNeeded); + if (pcbEncrypted) + *pcbEncrypted = cbEncrypted1 + cbEncrypted2; + } + } + } + + if (pCtx != NIL_RTCRCIPHERCTX) + RTCrCipherCtxFree(pCtx); + + return rc; +} + + +RTDECL(int) RTCrCipherDecryptEx(RTCRCIPHER hCipher, void const *pvKey, size_t cbKey, + void const *pvInitVector, size_t cbInitVector, + void const *pvAuthData, size_t cbAuthData, + void *pvTag, size_t cbTag, + void const *pvEncrypted, size_t cbEncrypted, + void *pvPlainText, size_t cbPlainText, size_t *pcbPlainText) +{ + size_t const cbNeeded = cbEncrypted; + if (pcbPlainText) + { + *pcbPlainText = cbNeeded; + AssertReturn(cbPlainText >= cbNeeded, VERR_BUFFER_OVERFLOW); + } + else + AssertReturn(cbPlainText == cbNeeded, VERR_INVALID_PARAMETER); + AssertReturn((size_t)(int)cbEncrypted == cbEncrypted && (int)cbEncrypted > 0, VERR_OUT_OF_RANGE); + + RTCRCIPHERCTXINT *pCtx = NIL_RTCRCIPHERCTX; + + int rc = RTCrCipherCtxDecryptInit(hCipher, pvKey, cbKey, pvInitVector, cbInitVector, + pvAuthData, cbAuthData, pvTag, cbTag, &pCtx); + if (RT_SUCCESS(rc)) + { + size_t cbDecrypted1 = 0; + rc = RTCrCipherCtxDecryptProcess(pCtx, pvEncrypted, cbEncrypted, pvPlainText, cbPlainText, &cbDecrypted1); + if (RT_SUCCESS(rc)) + { + size_t cbDecrypted2 = 0; + rc = RTCrCipherCtxDecryptFinish(pCtx, (unsigned char *)pvPlainText + cbDecrypted1, + &cbDecrypted2); + if (RT_SUCCESS(rc)) + { + Assert(cbDecrypted1 + cbDecrypted2 == cbNeeded); + if (pcbPlainText) + *pcbPlainText = cbDecrypted1 + cbDecrypted2; + } + } + } + + if (pCtx != NIL_RTCRCIPHERCTX) + RTCrCipherCtxFree(pCtx); + + return rc; +} + +#endif /* IPRT_WITH_OPENSSL */ + diff --git a/src/VBox/Runtime/common/crypto/digest-builtin.cpp b/src/VBox/Runtime/common/crypto/digest-builtin.cpp new file mode 100644 index 00000000..a82d92f2 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/digest-builtin.cpp @@ -0,0 +1,1166 @@ +/* $Id: digest-builtin.cpp $ */ +/** @file + * IPRT - Crypto - Cryptographic Hash / Message Digest API, Built-in providers. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/digest.h> + +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/md2.h> +#include <iprt/md4.h> +#include <iprt/md5.h> +#include <iprt/sha.h> +#include <iprt/crypto/pkix.h> + +#ifdef IPRT_WITH_OPENSSL +# include "internal/iprt-openssl.h" +# include "internal/openssl-pre.h" +# include <openssl/evp.h> +# include "internal/openssl-post.h" +#endif + + + +/* + * MD2 + */ +#ifndef IPRT_WITHOUT_DIGEST_MD2 + +/** @impl_interface_method{RTCRDIGESTDESC::pfnUpdate} */ +static DECLCALLBACK(void) rtCrDigestMd2_Update(void *pvState, const void *pvData, size_t cbData) +{ + RTMd2Update((PRTMD2CONTEXT)pvState, pvData, cbData); +} + +/** @impl_interface_method{RTCRDIGESTDESC::pfnFinal} */ +static DECLCALLBACK(void) rtCrDigestMd2_Final(void *pvState, uint8_t *pbHash) +{ + RTMd2Final((PRTMD2CONTEXT)pvState, pbHash); +} + +/** @impl_interface_method{RTCRDIGESTDESC::pfnInit} */ +static DECLCALLBACK(int) rtCrDigestMd2_Init(void *pvState, void *pvOpaque, bool fReInit) +{ + RT_NOREF_PV(fReInit); RT_NOREF_PV(pvOpaque); + AssertReturn(pvOpaque == NULL, VERR_INVALID_PARAMETER); + RTMd2Init((PRTMD2CONTEXT)pvState); + return VINF_SUCCESS; +} + +/** MD2 alias ODIs. */ +static const char * const g_apszMd2Aliases[] = +{ + RTCR_PKCS1_MD2_WITH_RSA_OID, + "1.3.14.3.2.24" /* OIW md2WithRSASignature */, + NULL +}; + +/** MD2 descriptor. */ +static RTCRDIGESTDESC const g_rtCrDigestMd2Desc = +{ + "md2", + "1.2.840.113549.2.2", + g_apszMd2Aliases, + RTDIGESTTYPE_MD2, + RTMD2_HASH_SIZE, + sizeof(RTMD2CONTEXT), + RTCRDIGESTDESC_F_DEPRECATED, + NULL, + NULL, + rtCrDigestMd2_Update, + rtCrDigestMd2_Final, + rtCrDigestMd2_Init, + NULL, + NULL, + NULL, + NULL, +}; +#endif /* !IPRT_WITHOUT_DIGEST_MD2 */ + + +/* + * MD4 + */ +#ifndef IPRT_WITHOUT_DIGEST_MD4 + +/** @impl_interface_method{RTCRDIGESTDESC::pfnUpdate} */ +static DECLCALLBACK(void) rtCrDigestMd4_Update(void *pvState, const void *pvData, size_t cbData) +{ + RTMd4Update((PRTMD4CONTEXT)pvState, pvData, cbData); +} + +/** @impl_interface_method{RTCRDIGESTDESC::pfnFinal} */ +static DECLCALLBACK(void) rtCrDigestMd4_Final(void *pvState, uint8_t *pbHash) +{ + RTMd4Final((PRTMD4CONTEXT)pvState, pbHash); +} + +/** @impl_interface_method{RTCRDIGESTDESC::pfnInit} */ +static DECLCALLBACK(int) rtCrDigestMd4_Init(void *pvState, void *pvOpaque, bool fReInit) +{ + RT_NOREF_PV(fReInit); RT_NOREF_PV(pvOpaque); + AssertReturn(pvOpaque == NULL, VERR_INVALID_PARAMETER); + RTMd4Init((PRTMD4CONTEXT)pvState); + return VINF_SUCCESS; +} + +/** MD4 alias ODIs. */ +static const char * const g_apszMd4Aliases[] = +{ + RTCR_PKCS1_MD4_WITH_RSA_OID, + NULL +}; + +/** MD4 descriptor. */ +static RTCRDIGESTDESC const g_rtCrDigestMd4Desc = +{ + "md4", + "1.2.840.113549.2.4", + g_apszMd4Aliases, + RTDIGESTTYPE_MD4, + RTMD4_HASH_SIZE, + sizeof(RTMD4CONTEXT), + RTCRDIGESTDESC_F_DEPRECATED | RTCRDIGESTDESC_F_COMPROMISED | RTCRDIGESTDESC_F_SERVERELY_COMPROMISED, + NULL, + NULL, + rtCrDigestMd4_Update, + rtCrDigestMd4_Final, + rtCrDigestMd4_Init, + NULL, + NULL, + NULL, + NULL, +}; + +#endif /* !IPRT_WITHOUT_DIGEST_MD4 */ + + +/* + * MD5 + */ +#ifndef IPRT_WITHOUT_DIGEST_MD5 + +/** @impl_interface_method{RTCRDIGESTDESC::pfnUpdate} */ +static DECLCALLBACK(void) rtCrDigestMd5_Update(void *pvState, const void *pvData, size_t cbData) +{ + RTMd5Update((PRTMD5CONTEXT)pvState, pvData, cbData); +} + +/** @impl_interface_method{RTCRDIGESTDESC::pfnFinal} */ +static DECLCALLBACK(void) rtCrDigestMd5_Final(void *pvState, uint8_t *pbHash) +{ + RTMd5Final(pbHash, (PRTMD5CONTEXT)pvState); +} + +/** @impl_interface_method{RTCRDIGESTDESC::pfnInit} */ +static DECLCALLBACK(int) rtCrDigestMd5_Init(void *pvState, void *pvOpaque, bool fReInit) +{ + RT_NOREF_PV(pvOpaque); RT_NOREF_PV(fReInit); + AssertReturn(pvOpaque == NULL, VERR_INVALID_PARAMETER); + RTMd5Init((PRTMD5CONTEXT)pvState); + return VINF_SUCCESS; +} + +/** MD5 alias ODIs. */ +static const char * const g_apszMd5Aliases[] = +{ + RTCR_PKCS1_MD5_WITH_RSA_OID, + "1.3.14.3.2.25" /* OIW md5WithRSASignature */, + NULL +}; + +/** MD5 descriptor. */ +static RTCRDIGESTDESC const g_rtCrDigestMd5Desc = +{ + "md5", + "1.2.840.113549.2.5", + g_apszMd5Aliases, + RTDIGESTTYPE_MD5, + RTMD5_HASH_SIZE, + sizeof(RTMD5CONTEXT), + RTCRDIGESTDESC_F_COMPROMISED, + NULL, + NULL, + rtCrDigestMd5_Update, + rtCrDigestMd5_Final, + rtCrDigestMd5_Init, + NULL, + NULL, + NULL, + NULL, +}; +#endif /* !IPRT_WITHOUT_DIGEST_MD5 */ + + +/* + * SHA-1 + */ + +/** @impl_interface_method{RTCRDIGESTDESC::pfnUpdate} */ +static DECLCALLBACK(void) rtCrDigestSha1_Update(void *pvState, const void *pvData, size_t cbData) +{ + RTSha1Update((PRTSHA1CONTEXT)pvState, pvData, cbData); +} + +/** @impl_interface_method{RTCRDIGESTDESC::pfnFinal} */ +static DECLCALLBACK(void) rtCrDigestSha1_Final(void *pvState, uint8_t *pbHash) +{ + RTSha1Final((PRTSHA1CONTEXT)pvState, pbHash); +} + +/** @impl_interface_method{RTCRDIGESTDESC::pfnInit} */ +static DECLCALLBACK(int) rtCrDigestSha1_Init(void *pvState, void *pvOpaque, bool fReInit) +{ + RT_NOREF_PV(pvOpaque); RT_NOREF_PV(fReInit); + AssertReturn(pvOpaque == NULL, VERR_INVALID_PARAMETER); + RTSha1Init((PRTSHA1CONTEXT)pvState); + return VINF_SUCCESS; +} + +/** SHA-1 alias ODIs. */ +static const char * const g_apszSha1Aliases[] = +{ + RTCR_PKCS1_SHA1_WITH_RSA_OID, + "1.3.14.3.2.29" /* OIW sha1WithRSASignature */, + NULL +}; + +/** SHA-1 descriptor. */ +static RTCRDIGESTDESC const g_rtCrDigestSha1Desc = +{ + "sha-1", + "1.3.14.3.2.26", + g_apszSha1Aliases, + RTDIGESTTYPE_SHA1, + RTSHA1_HASH_SIZE, + sizeof(RTSHA1CONTEXT), + RTCRDIGESTDESC_F_DEPRECATED, + NULL, + NULL, + rtCrDigestSha1_Update, + rtCrDigestSha1_Final, + rtCrDigestSha1_Init, + NULL, + NULL, + NULL, + NULL, +}; + + +/* + * SHA-256 + */ + +/** @impl_interface_method{RTCRDIGESTDESC::pfnUpdate} */ +static DECLCALLBACK(void) rtCrDigestSha256_Update(void *pvState, const void *pvData, size_t cbData) +{ + RTSha256Update((PRTSHA256CONTEXT)pvState, pvData, cbData); +} + +/** @impl_interface_method{RTCRDIGESTDESC::pfnFinal} */ +static DECLCALLBACK(void) rtCrDigestSha256_Final(void *pvState, uint8_t *pbHash) +{ + RTSha256Final((PRTSHA256CONTEXT)pvState, pbHash); +} + +/** @impl_interface_method{RTCRDIGESTDESC::pfnInit} */ +static DECLCALLBACK(int) rtCrDigestSha256_Init(void *pvState, void *pvOpaque, bool fReInit) +{ + RT_NOREF_PV(pvOpaque); RT_NOREF_PV(fReInit); + AssertReturn(pvOpaque == NULL, VERR_INVALID_PARAMETER); + RTSha256Init((PRTSHA256CONTEXT)pvState); + return VINF_SUCCESS; +} + +/** SHA-256 alias ODIs. */ +static const char * const g_apszSha256Aliases[] = +{ + RTCR_PKCS1_SHA256_WITH_RSA_OID, + NULL +}; + +/** SHA-256 descriptor. */ +static RTCRDIGESTDESC const g_rtCrDigestSha256Desc = +{ + "sha-256", + "2.16.840.1.101.3.4.2.1", + g_apszSha256Aliases, + RTDIGESTTYPE_SHA256, + RTSHA256_HASH_SIZE, + sizeof(RTSHA256CONTEXT), + 0, + NULL, + NULL, + rtCrDigestSha256_Update, + rtCrDigestSha256_Final, + rtCrDigestSha256_Init, + NULL, + NULL, + NULL, + NULL, +}; + + +/* + * SHA-512 + */ + +/** @impl_interface_method{RTCRDIGESTDESC::pfnUpdate} */ +static DECLCALLBACK(void) rtCrDigestSha512_Update(void *pvState, const void *pvData, size_t cbData) +{ + RTSha512Update((PRTSHA512CONTEXT)pvState, pvData, cbData); +} + +/** @impl_interface_method{RTCRDIGESTDESC::pfnFinal} */ +static DECLCALLBACK(void) rtCrDigestSha512_Final(void *pvState, uint8_t *pbHash) +{ + RTSha512Final((PRTSHA512CONTEXT)pvState, pbHash); +} + +/** @impl_interface_method{RTCRDIGESTDESC::pfnInit} */ +static DECLCALLBACK(int) rtCrDigestSha512_Init(void *pvState, void *pvOpaque, bool fReInit) +{ + RT_NOREF_PV(pvOpaque); RT_NOREF_PV(fReInit); + AssertReturn(pvOpaque == NULL, VERR_INVALID_PARAMETER); + RTSha512Init((PRTSHA512CONTEXT)pvState); + return VINF_SUCCESS; +} + +/** SHA-512 alias ODIs. */ +static const char * const g_apszSha512Aliases[] = +{ + RTCR_PKCS1_SHA512_WITH_RSA_OID, + NULL +}; + +/** SHA-512 descriptor. */ +static RTCRDIGESTDESC const g_rtCrDigestSha512Desc = +{ + "sha-512", + "2.16.840.1.101.3.4.2.3", + g_apszSha512Aliases, + RTDIGESTTYPE_SHA512, + RTSHA512_HASH_SIZE, + sizeof(RTSHA512CONTEXT), + 0, + NULL, + NULL, + rtCrDigestSha512_Update, + rtCrDigestSha512_Final, + rtCrDigestSha512_Init, + NULL, + NULL, + NULL, + NULL, +}; + + +/* + * SHA-224 + */ + +/** @impl_interface_method{RTCRDIGESTDESC::pfnUpdate} */ +static DECLCALLBACK(void) rtCrDigestSha224_Update(void *pvState, const void *pvData, size_t cbData) +{ + RTSha224Update((PRTSHA224CONTEXT)pvState, pvData, cbData); +} + +/** @impl_interface_method{RTCRDIGESTDESC::pfnFinal} */ +static DECLCALLBACK(void) rtCrDigestSha224_Final(void *pvState, uint8_t *pbHash) +{ + RTSha224Final((PRTSHA224CONTEXT)pvState, pbHash); +} + +/** @impl_interface_method{RTCRDIGESTDESC::pfnInit} */ +static DECLCALLBACK(int) rtCrDigestSha224_Init(void *pvState, void *pvOpaque, bool fReInit) +{ + RT_NOREF_PV(pvOpaque); RT_NOREF_PV(fReInit); + AssertReturn(pvOpaque == NULL, VERR_INVALID_PARAMETER); + RTSha224Init((PRTSHA224CONTEXT)pvState); + return VINF_SUCCESS; +} + +/** SHA-224 alias ODIs. */ +static const char * const g_apszSha224Aliases[] = +{ + RTCR_PKCS1_SHA224_WITH_RSA_OID, + NULL +}; + +/** SHA-224 descriptor. */ +static RTCRDIGESTDESC const g_rtCrDigestSha224Desc = +{ + "sha-224", + "2.16.840.1.101.3.4.2.4", + g_apszSha224Aliases, + RTDIGESTTYPE_SHA224, + RTSHA224_HASH_SIZE, + sizeof(RTSHA224CONTEXT), + 0, + NULL, + NULL, + rtCrDigestSha224_Update, + rtCrDigestSha224_Final, + rtCrDigestSha224_Init, + NULL, + NULL, + NULL, + NULL, +}; + + +/* + * SHA-384 + */ + +/** @impl_interface_method{RTCRDIGESTDESC::pfnUpdate} */ +static DECLCALLBACK(void) rtCrDigestSha384_Update(void *pvState, const void *pvData, size_t cbData) +{ + RTSha384Update((PRTSHA384CONTEXT)pvState, pvData, cbData); +} + +/** @impl_interface_method{RTCRDIGESTDESC::pfnFinal} */ +static DECLCALLBACK(void) rtCrDigestSha384_Final(void *pvState, uint8_t *pbHash) +{ + RTSha384Final((PRTSHA384CONTEXT)pvState, pbHash); +} + +/** @impl_interface_method{RTCRDIGESTDESC::pfnInit} */ +static DECLCALLBACK(int) rtCrDigestSha384_Init(void *pvState, void *pvOpaque, bool fReInit) +{ + RT_NOREF_PV(pvOpaque); RT_NOREF_PV(fReInit); + AssertReturn(pvOpaque == NULL, VERR_INVALID_PARAMETER); + RTSha384Init((PRTSHA384CONTEXT)pvState); + return VINF_SUCCESS; +} + +/** SHA-384 alias ODIs. */ +static const char * const g_apszSha384Aliases[] = +{ + RTCR_PKCS1_SHA384_WITH_RSA_OID, + NULL +}; + +/** SHA-384 descriptor. */ +static RTCRDIGESTDESC const g_rtCrDigestSha384Desc = +{ + "sha-384", + "2.16.840.1.101.3.4.2.2", + g_apszSha384Aliases, + RTDIGESTTYPE_SHA384, + RTSHA384_HASH_SIZE, + sizeof(RTSHA384CONTEXT), + 0, + NULL, + NULL, + rtCrDigestSha384_Update, + rtCrDigestSha384_Final, + rtCrDigestSha384_Init, + NULL, + NULL, + NULL, + NULL, +}; + + +#ifndef IPRT_WITHOUT_SHA512T224 +/* + * SHA-512/224 + */ + +/** @impl_interface_method{RTCRDIGESTDESC::pfnUpdate} */ +static DECLCALLBACK(void) rtCrDigestSha512t224_Update(void *pvState, const void *pvData, size_t cbData) +{ + RTSha512t224Update((PRTSHA512T224CONTEXT)pvState, pvData, cbData); +} + +/** @impl_interface_method{RTCRDIGESTDESC::pfnFinal} */ +static DECLCALLBACK(void) rtCrDigestSha512t224_Final(void *pvState, uint8_t *pbHash) +{ + RTSha512t224Final((PRTSHA512T224CONTEXT)pvState, pbHash); +} + +/** @impl_interface_method{RTCRDIGESTDESC::pfnInit} */ +static DECLCALLBACK(int) rtCrDigestSha512t224_Init(void *pvState, void *pvOpaque, bool fReInit) +{ + RT_NOREF_PV(pvOpaque); RT_NOREF_PV(fReInit); + AssertReturn(pvOpaque == NULL, VERR_INVALID_PARAMETER); + RTSha512t224Init((PRTSHA512T224CONTEXT)pvState); + return VINF_SUCCESS; +} + +/** SHA-512/224 alias ODIs. */ +static const char * const g_apszSha512t224Aliases[] = +{ + RTCR_PKCS1_SHA512T224_WITH_RSA_OID, + NULL +}; + +/** SHA-512/224 descriptor. */ +static RTCRDIGESTDESC const g_rtCrDigestSha512t224Desc = +{ + "sha-512/224", + "2.16.840.1.101.3.4.2.5", + g_apszSha512t224Aliases, + RTDIGESTTYPE_SHA512T224, + RTSHA512T224_HASH_SIZE, + sizeof(RTSHA512T224CONTEXT), + 0, + NULL, + NULL, + rtCrDigestSha512t224_Update, + rtCrDigestSha512t224_Final, + rtCrDigestSha512t224_Init, + NULL, + NULL, + NULL, + NULL, +}; +#endif /* !IPRT_WITHOUT_SHA512T224 */ + + +#ifndef IPRT_WITHOUT_SHA512T256 +/* + * SHA-512/256 + */ + +/** @impl_interface_method{RTCRDIGESTDESC::pfnUpdate} */ +static DECLCALLBACK(void) rtCrDigestSha512t256_Update(void *pvState, const void *pvData, size_t cbData) +{ + RTSha512t256Update((PRTSHA512T256CONTEXT)pvState, pvData, cbData); +} + +/** @impl_interface_method{RTCRDIGESTDESC::pfnFinal} */ +static DECLCALLBACK(void) rtCrDigestSha512t256_Final(void *pvState, uint8_t *pbHash) +{ + RTSha512t256Final((PRTSHA512T256CONTEXT)pvState, pbHash); +} + +/** @impl_interface_method{RTCRDIGESTDESC::pfnInit} */ +static DECLCALLBACK(int) rtCrDigestSha512t256_Init(void *pvState, void *pvOpaque, bool fReInit) +{ + RT_NOREF_PV(pvOpaque); RT_NOREF_PV(fReInit); + AssertReturn(pvOpaque == NULL, VERR_INVALID_PARAMETER); + RTSha512t256Init((PRTSHA512T256CONTEXT)pvState); + return VINF_SUCCESS; +} + +/** SHA-512/256 alias ODIs. */ +static const char * const g_apszSha512t256Aliases[] = +{ + RTCR_PKCS1_SHA512T256_WITH_RSA_OID, + NULL +}; + +/** SHA-512/256 descriptor. */ +static RTCRDIGESTDESC const g_rtCrDigestSha512t256Desc = +{ + "sha-512/256", + "2.16.840.1.101.3.4.2.6", + g_apszSha512t256Aliases, + RTDIGESTTYPE_SHA512T256, + RTSHA512T256_HASH_SIZE, + sizeof(RTSHA512T256CONTEXT), + 0, + NULL, + NULL, + rtCrDigestSha512t256_Update, + rtCrDigestSha512t256_Final, + rtCrDigestSha512t256_Init, + NULL, + NULL, + NULL, + NULL, +}; +#endif /* !IPRT_WITHOUT_SHA512T256 */ + +#ifndef IPRT_WITHOUT_SHA3 + +/* + * SHA3-224 + */ + +/** @impl_interface_method{RTCRDIGESTDESC::pfnUpdate} */ +static DECLCALLBACK(void) rtCrDigestSha3t224_Update(void *pvState, const void *pvData, size_t cbData) +{ + int rc = RTSha3t224Update((PRTSHA3T224CONTEXT)pvState, pvData, cbData); + AssertRC(rc); +} + +/** @impl_interface_method{RTCRDIGESTDESC::pfnFinal} */ +static DECLCALLBACK(void) rtCrDigestSha3t224_Final(void *pvState, uint8_t *pbHash) +{ + int rc = RTSha3t224Final((PRTSHA3T224CONTEXT)pvState, pbHash); + AssertRC(rc); +} + +/** @impl_interface_method{RTCRDIGESTDESC::pfnInit} */ +static DECLCALLBACK(int) rtCrDigestSha3t224_Init(void *pvState, void *pvOpaque, bool fReInit) +{ + RT_NOREF_PV(pvOpaque); + AssertReturn(pvOpaque == NULL, VERR_INVALID_PARAMETER); + if (fReInit) + RTSha3t224Cleanup((PRTSHA3T224CONTEXT)pvState); + return RTSha3t224Init((PRTSHA3T224CONTEXT)pvState); +} + +/** @impl_interface_method{RTCRDIGESTDESC::pfnDelete} */ +static DECLCALLBACK(void) rtCrDigestSha3t224_Delete(void *pvState) +{ + RTSha3t224Cleanup((PRTSHA3T224CONTEXT)pvState); +} + +/** @impl_interface_method{RTCRDIGESTDESC::pfnDelete} */ +static DECLCALLBACK(int) rtCrDigestSha3t224_Clone(void *pvState, void const *pvSrcState) +{ + return RTSha3t224Clone((PRTSHA3T224CONTEXT)pvState, (PRTSHA3T224CONTEXT)pvSrcState); +} + +/** SHA3-224 alias ODIs. */ +static const char * const g_apszSha3t224Aliases[] = +{ + "2.16.840.1.101.3.4.3.13", + NULL +}; + +/** SHA3-224 descriptor. */ +static RTCRDIGESTDESC const g_rtCrDigestSha3t224Desc = +{ + "sha3-224", + "2.16.840.1.101.3.4.2.7", + g_apszSha3t224Aliases, + RTDIGESTTYPE_SHA3_224, + RTSHA3_224_HASH_SIZE, + sizeof(RTSHA3T224CONTEXT), + 0, + NULL, + NULL, + rtCrDigestSha3t224_Update, + rtCrDigestSha3t224_Final, + rtCrDigestSha3t224_Init, + rtCrDigestSha3t224_Delete, + rtCrDigestSha3t224_Clone, + NULL, + NULL, +}; + + +/* + * SHA3-256 + */ + +/** @impl_interface_method{RTCRDIGESTDESC::pfnUpdate} */ +static DECLCALLBACK(void) rtCrDigestSha3t256_Update(void *pvState, const void *pvData, size_t cbData) +{ + int rc = RTSha3t256Update((PRTSHA3T256CONTEXT)pvState, pvData, cbData); + AssertRC(rc); +} + +/** @impl_interface_method{RTCRDIGESTDESC::pfnFinal} */ +static DECLCALLBACK(void) rtCrDigestSha3t256_Final(void *pvState, uint8_t *pbHash) +{ + int rc = RTSha3t256Final((PRTSHA3T256CONTEXT)pvState, pbHash); + AssertRC(rc); +} + +/** @impl_interface_method{RTCRDIGESTDESC::pfnInit} */ +static DECLCALLBACK(int) rtCrDigestSha3t256_Init(void *pvState, void *pvOpaque, bool fReInit) +{ + RT_NOREF_PV(pvOpaque); + AssertReturn(pvOpaque == NULL, VERR_INVALID_PARAMETER); + if (fReInit) + RTSha3t256Cleanup((PRTSHA3T256CONTEXT)pvState); + return RTSha3t256Init((PRTSHA3T256CONTEXT)pvState); +} + +/** @impl_interface_method{RTCRDIGESTDESC::pfnDelete} */ +static DECLCALLBACK(void) rtCrDigestSha3t256_Delete(void *pvState) +{ + RTSha3t256Cleanup((PRTSHA3T256CONTEXT)pvState); +} + +/** @impl_interface_method{RTCRDIGESTDESC::pfnDelete} */ +static DECLCALLBACK(int) rtCrDigestSha3t256_Clone(void *pvState, void const *pvSrcState) +{ + return RTSha3t256Clone((PRTSHA3T256CONTEXT)pvState, (PRTSHA3T256CONTEXT)pvSrcState); +} + +/** SHA3-256 alias ODIs. */ +static const char * const g_apszSha3t256Aliases[] = +{ + "2.16.840.1.101.3.4.3.14", + NULL +}; + +/** SHA3-256 descriptor. */ +static RTCRDIGESTDESC const g_rtCrDigestSha3t256Desc = +{ + "sha3-256", + "2.16.840.1.101.3.4.2.8", + g_apszSha3t256Aliases, + RTDIGESTTYPE_SHA3_256, + RTSHA3_256_HASH_SIZE, + sizeof(RTSHA3T256CONTEXT), + 0, + NULL, + NULL, + rtCrDigestSha3t256_Update, + rtCrDigestSha3t256_Final, + rtCrDigestSha3t256_Init, + rtCrDigestSha3t256_Delete, + rtCrDigestSha3t256_Clone, + NULL, + NULL, +}; + + +/* + * SHA3-384 + */ + +/** @impl_interface_method{RTCRDIGESTDESC::pfnUpdate} */ +static DECLCALLBACK(void) rtCrDigestSha3t384_Update(void *pvState, const void *pvData, size_t cbData) +{ + int rc = RTSha3t384Update((PRTSHA3T384CONTEXT)pvState, pvData, cbData); + AssertRC(rc); +} + +/** @impl_interface_method{RTCRDIGESTDESC::pfnFinal} */ +static DECLCALLBACK(void) rtCrDigestSha3t384_Final(void *pvState, uint8_t *pbHash) +{ + int rc = RTSha3t384Final((PRTSHA3T384CONTEXT)pvState, pbHash); + AssertRC(rc); +} + +/** @impl_interface_method{RTCRDIGESTDESC::pfnInit} */ +static DECLCALLBACK(int) rtCrDigestSha3t384_Init(void *pvState, void *pvOpaque, bool fReInit) +{ + RT_NOREF_PV(pvOpaque); + AssertReturn(pvOpaque == NULL, VERR_INVALID_PARAMETER); + if (fReInit) + RTSha3t384Cleanup((PRTSHA3T384CONTEXT)pvState); + return RTSha3t384Init((PRTSHA3T384CONTEXT)pvState); +} + +/** @impl_interface_method{RTCRDIGESTDESC::pfnDelete} */ +static DECLCALLBACK(void) rtCrDigestSha3t384_Delete(void *pvState) +{ + RTSha3t384Cleanup((PRTSHA3T384CONTEXT)pvState); +} + +/** @impl_interface_method{RTCRDIGESTDESC::pfnDelete} */ +static DECLCALLBACK(int) rtCrDigestSha3t384_Clone(void *pvState, void const *pvSrcState) +{ + return RTSha3t384Clone((PRTSHA3T384CONTEXT)pvState, (PRTSHA3T384CONTEXT)pvSrcState); +} + +/** SHA3-384 alias ODIs. */ +static const char * const g_apszSha3t384Aliases[] = +{ + "2.16.840.1.101.3.4.3.15", + NULL +}; + +/** SHA3-384 descriptor. */ +static RTCRDIGESTDESC const g_rtCrDigestSha3t384Desc = +{ + "sha3-384", + "2.16.840.1.101.3.4.2.9", + g_apszSha3t384Aliases, + RTDIGESTTYPE_SHA3_384, + RTSHA3_384_HASH_SIZE, + sizeof(RTSHA3T384CONTEXT), + 0, + NULL, + NULL, + rtCrDigestSha3t384_Update, + rtCrDigestSha3t384_Final, + rtCrDigestSha3t384_Init, + rtCrDigestSha3t384_Delete, + rtCrDigestSha3t384_Clone, + NULL, + NULL, +}; + + +/* + * SHA3-512 + */ + +/** @impl_interface_method{RTCRDIGESTDESC::pfnUpdate} */ +static DECLCALLBACK(void) rtCrDigestSha3t512_Update(void *pvState, const void *pvData, size_t cbData) +{ + int rc = RTSha3t512Update((PRTSHA3T512CONTEXT)pvState, pvData, cbData); + AssertRC(rc); +} + +/** @impl_interface_method{RTCRDIGESTDESC::pfnFinal} */ +static DECLCALLBACK(void) rtCrDigestSha3t512_Final(void *pvState, uint8_t *pbHash) +{ + int rc = RTSha3t512Final((PRTSHA3T512CONTEXT)pvState, pbHash); + AssertRC(rc); +} + +/** @impl_interface_method{RTCRDIGESTDESC::pfnInit} */ +static DECLCALLBACK(int) rtCrDigestSha3t512_Init(void *pvState, void *pvOpaque, bool fReInit) +{ + RT_NOREF_PV(pvOpaque); + AssertReturn(pvOpaque == NULL, VERR_INVALID_PARAMETER); + if (fReInit) + RTSha3t512Cleanup((PRTSHA3T512CONTEXT)pvState); + return RTSha3t512Init((PRTSHA3T512CONTEXT)pvState); +} + +/** @impl_interface_method{RTCRDIGESTDESC::pfnDelete} */ +static DECLCALLBACK(void) rtCrDigestSha3t512_Delete(void *pvState) +{ + RTSha3t512Cleanup((PRTSHA3T512CONTEXT)pvState); +} + +/** @impl_interface_method{RTCRDIGESTDESC::pfnDelete} */ +static DECLCALLBACK(int) rtCrDigestSha3t512_Clone(void *pvState, void const *pvSrcState) +{ + return RTSha3t512Clone((PRTSHA3T512CONTEXT)pvState, (PRTSHA3T512CONTEXT)pvSrcState); +} + +/** SHA3-512 alias ODIs. */ +static const char * const g_apszSha3t512Aliases[] = +{ + "2.16.840.1.101.3.4.3.16", + NULL +}; + +/** SHA3-512 descriptor. */ +static RTCRDIGESTDESC const g_rtCrDigestSha3t512Desc = +{ + "sha3-512", + "2.16.840.1.101.3.4.2.10", + g_apszSha3t512Aliases, + RTDIGESTTYPE_SHA3_512, + RTSHA3_512_HASH_SIZE, + sizeof(RTSHA3T512CONTEXT), + 0, + NULL, + NULL, + rtCrDigestSha3t512_Update, + rtCrDigestSha3t512_Final, + rtCrDigestSha3t512_Init, + rtCrDigestSha3t512_Delete, + rtCrDigestSha3t512_Clone, + NULL, + NULL, +}; + +#endif /* !IPRT_WITHOUT_SHA3 */ + + +/** + * Array of built in message digest vtables. + */ +static PCRTCRDIGESTDESC const g_apDigestOps[] = +{ +#ifndef IPRT_WITHOUT_DIGEST_MD2 + &g_rtCrDigestMd2Desc, +#endif +#ifndef IPRT_WITHOUT_DIGEST_MD4 + &g_rtCrDigestMd4Desc, +#endif +#ifndef IPRT_WITHOUT_DIGEST_MD5 + &g_rtCrDigestMd5Desc, +#endif + &g_rtCrDigestSha1Desc, + &g_rtCrDigestSha256Desc, + &g_rtCrDigestSha512Desc, + &g_rtCrDigestSha224Desc, + &g_rtCrDigestSha384Desc, +#ifndef IPRT_WITHOUT_SHA512T224 + &g_rtCrDigestSha512t224Desc, +#endif +#ifndef IPRT_WITHOUT_SHA512T256 + &g_rtCrDigestSha512t256Desc, +#endif +#ifndef IPRT_WITHOUT_SHA3 + &g_rtCrDigestSha3t224Desc, + &g_rtCrDigestSha3t256Desc, + &g_rtCrDigestSha3t384Desc, + &g_rtCrDigestSha3t512Desc, +#endif +}; + + +#ifdef IPRT_WITH_OPENSSL +/* + * OpenSSL EVP. + */ + +# if OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(LIBRESSL_VERSION_NUMBER) +/** @impl_interface_method{RTCRDIGESTDESC::pfnNew} */ +static DECLCALLBACK(void*) rtCrDigestOsslEvp_New(void) +{ + return EVP_MD_CTX_new(); +} + +static DECLCALLBACK(void) rtCrDigestOsslEvp_Free(void *pvState) +{ + EVP_MD_CTX_free((EVP_MD_CTX*)pvState); +} + +# endif + +/** @impl_interface_method{RTCRDIGESTDESC::pfnUpdate} */ +static DECLCALLBACK(void) rtCrDigestOsslEvp_Update(void *pvState, const void *pvData, size_t cbData) +{ + EVP_DigestUpdate((EVP_MD_CTX *)pvState, pvData, cbData); +} + +/** @impl_interface_method{RTCRDIGESTDESC::pfnFinal} */ +static DECLCALLBACK(void) rtCrDigestOsslEvp_Final(void *pvState, uint8_t *pbHash) +{ + unsigned int cbHash = EVP_MAX_MD_SIZE; + EVP_DigestFinal((EVP_MD_CTX *)pvState, (unsigned char *)pbHash, &cbHash); +} + +/** @impl_interface_method{RTCRDIGESTDESC::pfnInit} */ +static DECLCALLBACK(int) rtCrDigestOsslEvp_Init(void *pvState, void *pvOpaque, bool fReInit) +{ + EVP_MD_CTX *pThis = (EVP_MD_CTX *)pvState; + EVP_MD const *pEvpType = (EVP_MD const *)pvOpaque; + + if (fReInit) + { + pEvpType = EVP_MD_CTX_md(pThis); +# if OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(LIBRESSL_VERSION_NUMBER) + EVP_MD_CTX_reset(pThis); +# else + EVP_MD_CTX_cleanup(pThis); +# endif + } + + AssertPtrReturn(pEvpType, VERR_INVALID_PARAMETER); +# if OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(LIBRESSL_VERSION_NUMBER) + Assert(EVP_MD_block_size(pEvpType)); +# else + Assert(pEvpType->md_size); +# endif + if (EVP_DigestInit(pThis, pEvpType)) + return VINF_SUCCESS; + return VERR_CR_DIGEST_OSSL_DIGEST_INIT_ERROR; +} + + +/** @impl_interface_method{RTCRDIGESTDESC::pfn} */ +static DECLCALLBACK(void) rtCrDigestOsslEvp_Delete(void *pvState) +{ + EVP_MD_CTX *pThis = (EVP_MD_CTX *)pvState; +# if OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(LIBRESSL_VERSION_NUMBER) + EVP_MD_CTX_reset(pThis); +# else + EVP_MD_CTX_cleanup(pThis); +# endif +} + + +/** @impl_interface_method{RTCRDIGESTDESC::pfnClone} */ +static DECLCALLBACK(int) rtCrDigestOsslEvp_Clone(void *pvState, void const *pvSrcState) +{ + EVP_MD_CTX *pThis = (EVP_MD_CTX *)pvState; + EVP_MD_CTX const *pSrc = (EVP_MD_CTX const *)pvSrcState; + + if (EVP_MD_CTX_copy(pThis, pSrc)) + return VINF_SUCCESS; + return VERR_CR_DIGEST_OSSL_DIGEST_CTX_COPY_ERROR; +} + + +/** @impl_interface_method{RTCRDIGESTDESC::pfnGetHashSize} */ +static DECLCALLBACK(uint32_t) rtCrDigestOsslEvp_GetHashSize(void *pvState) +{ + EVP_MD_CTX *pThis = (EVP_MD_CTX *)pvState; + return EVP_MD_size(EVP_MD_CTX_md(pThis)); +} + + +/** @impl_interface_method{RTCRDIGESTDESC::pfnGetHashSize} */ +static DECLCALLBACK(RTDIGESTTYPE) rtCrDigestOsslEvp_GetDigestType(void *pvState) +{ + RT_NOREF_PV(pvState); //EVP_MD_CTX *pThis = (EVP_MD_CTX *)pvState; + /** @todo figure which digest algorithm it is! */ + return RTDIGESTTYPE_UNKNOWN; +} + + +/** Descriptor for the OpenSSL EVP base message digest provider. */ +static RTCRDIGESTDESC const g_rtCrDigestOpenSslDesc = +{ + "OpenSSL EVP", + NULL, + NULL, + RTDIGESTTYPE_UNKNOWN, + EVP_MAX_MD_SIZE, +# if OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(LIBRESSL_VERSION_NUMBER) + 0, +# else + sizeof(EVP_MD_CTX), +# endif + 0, +# if OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(LIBRESSL_VERSION_NUMBER) + rtCrDigestOsslEvp_New, + rtCrDigestOsslEvp_Free, +# else + NULL, + NULL, +# endif + rtCrDigestOsslEvp_Update, + rtCrDigestOsslEvp_Final, + rtCrDigestOsslEvp_Init, + rtCrDigestOsslEvp_Delete, + rtCrDigestOsslEvp_Clone, + rtCrDigestOsslEvp_GetHashSize, + rtCrDigestOsslEvp_GetDigestType +}; + +#endif /* IPRT_WITH_OPENSSL */ + + +RTDECL(PCRTCRDIGESTDESC) RTCrDigestFindByObjIdString(const char *pszObjId, void **ppvOpaque) +{ + if (ppvOpaque) + *ppvOpaque = NULL; + + /* + * Primary OIDs. + */ + uint32_t i = RT_ELEMENTS(g_apDigestOps); + while (i-- > 0) + if (strcmp(g_apDigestOps[i]->pszObjId, pszObjId) == 0) + return g_apDigestOps[i]; + + /* + * Alias OIDs. + */ + i = RT_ELEMENTS(g_apDigestOps); + while (i-- > 0) + { + const char * const *ppszAliases = g_apDigestOps[i]->papszObjIdAliases; + if (ppszAliases) + for (; *ppszAliases; ppszAliases++) + if (strcmp(*ppszAliases, pszObjId) == 0) + return g_apDigestOps[i]; + } + +#ifdef IPRT_WITH_OPENSSL + /* + * Try EVP and see if it knows the algorithm. + */ + if (ppvOpaque) + { + rtCrOpenSslInit(); + int iAlgoNid = OBJ_txt2nid(pszObjId); + if (iAlgoNid != NID_undef) + { + const char *pszAlogSn = OBJ_nid2sn(iAlgoNid); + const EVP_MD *pEvpMdType = EVP_get_digestbyname(pszAlogSn); + if (pEvpMdType) + { + /* + * Return the OpenSSL provider descriptor and the EVP_MD address. + */ +# if OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(LIBRESSL_VERSION_NUMBER) + Assert(EVP_MD_block_size(pEvpMdType)); +# else + Assert(pEvpMdType->md_size); +# endif + *ppvOpaque = (void *)pEvpMdType; + return &g_rtCrDigestOpenSslDesc; + } + } + } +#endif + return NULL; +} + + +RTDECL(PCRTCRDIGESTDESC) RTCrDigestFindByObjId(PCRTASN1OBJID pObjId, void **ppvOpaque) +{ + return RTCrDigestFindByObjIdString(pObjId->szObjId, ppvOpaque); +} + + +RTDECL(int) RTCrDigestCreateByObjIdString(PRTCRDIGEST phDigest, const char *pszObjId) +{ + void *pvOpaque; + PCRTCRDIGESTDESC pDesc = RTCrDigestFindByObjIdString(pszObjId, &pvOpaque); + if (pDesc) + return RTCrDigestCreate(phDigest, pDesc, pvOpaque); + return VERR_NOT_FOUND; +} + + +RTDECL(int) RTCrDigestCreateByObjId(PRTCRDIGEST phDigest, PCRTASN1OBJID pObjId) +{ + void *pvOpaque; + PCRTCRDIGESTDESC pDesc = RTCrDigestFindByObjId(pObjId, &pvOpaque); + if (pDesc) + return RTCrDigestCreate(phDigest, pDesc, pvOpaque); + return VERR_NOT_FOUND; +} + + +RTDECL(PCRTCRDIGESTDESC) RTCrDigestFindByType(RTDIGESTTYPE enmDigestType) +{ + AssertReturn(enmDigestType > RTDIGESTTYPE_INVALID && enmDigestType <= RTDIGESTTYPE_END, NULL); + + uint32_t i = RT_ELEMENTS(g_apDigestOps); + while (i-- > 0) + if (g_apDigestOps[i]->enmType == enmDigestType) + return g_apDigestOps[i]; + return NULL; +} + + +RTDECL(int) RTCrDigestCreateByType(PRTCRDIGEST phDigest, RTDIGESTTYPE enmDigestType) +{ + PCRTCRDIGESTDESC pDesc = RTCrDigestFindByType(enmDigestType); + if (pDesc) + return RTCrDigestCreate(phDigest, pDesc, NULL); + return VERR_NOT_FOUND; +} + diff --git a/src/VBox/Runtime/common/crypto/digest-core.cpp b/src/VBox/Runtime/common/crypto/digest-core.cpp new file mode 100644 index 00000000..daa62a79 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/digest-core.cpp @@ -0,0 +1,499 @@ +/* $Id: digest-core.cpp $ */ +/** @file + * IPRT - Crypto - Cryptographic Hash / Message Digest API + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/digest.h> + +#include <iprt/asm.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/crypto/x509.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Generic message digest instance. + */ +typedef struct RTCRDIGESTINT +{ + /** Magic value (RTCRDIGESTINT_MAGIC). */ + uint32_t u32Magic; + /** Reference counter. */ + uint32_t volatile cRefs; + /** Pointer to the message digest descriptor. */ + PCRTCRDIGESTDESC pDesc; + /** The offset into abState of the storage space . At + * least RTCRDIGESTDESC::cbHash bytes is available at that location. */ + uint32_t offHash; + /** State. */ + uint32_t uState; + /** The number of bytes consumed. */ + uint64_t cbConsumed; + /** Pointer to the data specific to the message digest algorithm. Points + * either to &abState[0] or to memory allocated with pDesc->pfnNew. */ + void *pvState; + /** Opaque data specific to the message digest algorithm, size given by + * RTCRDIGESTDESC::cbState. This is followed by space for the final hash + * at offHash with size RTCRDIGESTDESC::cbHash. The data specific to the + * message digest algorithm can also be 0. In this case, pDesc->pfnNew() + * and pDesc->pfnFree() must not be NULL. */ + uint8_t abState[1]; +} RTCRDIGESTINT; +/** Pointer to a message digest instance. */ +typedef RTCRDIGESTINT *PRTCRDIGESTINT; + +/** Magic value for RTCRDIGESTINT::u32Magic (Ralph C. Merkle). */ +#define RTCRDIGESTINT_MAGIC UINT32_C(0x19520202) + +/** @name RTCRDIGESTINT::uState values. + * @{ */ +/** Ready for more data. */ +#define RTCRDIGEST_STATE_READY UINT32_C(1) +/** The hash has been finalized and can be found at offHash. */ +#define RTCRDIGEST_STATE_FINAL UINT32_C(2) +/** Busted state, can happen after re-init. */ +#define RTCRDIGEST_STATE_BUSTED UINT32_C(3) +/** @} */ + + + +/** + * Used for successful returns which wants to hit at digest security. + * + * @retval VINF_SUCCESS + * @retval VINF_CR_DIGEST_DEPRECATED + * @retval VINF_CR_DIGEST_COMPROMISED + * @retval VINF_CR_DIGEST_SEVERELY_COMPROMISED + * @param pDesc The digest descriptor. + */ +DECLINLINE(int) rtCrDigestSuccessWithDigestWarnings(PCRTCRDIGESTDESC pDesc) +{ + uint32_t const fFlags = pDesc->fFlags + & (RTCRDIGESTDESC_F_DEPRECATED | RTCRDIGESTDESC_F_COMPROMISED | RTCRDIGESTDESC_F_SERVERELY_COMPROMISED); + if (!fFlags) + return VINF_SUCCESS; + if (fFlags & RTCRDIGESTDESC_F_SERVERELY_COMPROMISED) + return VINF_CR_DIGEST_SEVERELY_COMPROMISED; + if (fFlags & RTCRDIGESTDESC_F_COMPROMISED) + return VINF_CR_DIGEST_COMPROMISED; + return VINF_CR_DIGEST_DEPRECATED; +} + + +RTDECL(int) RTCrDigestCreate(PRTCRDIGEST phDigest, PCRTCRDIGESTDESC pDesc, void *pvOpaque) +{ + AssertPtrReturn(phDigest, VERR_INVALID_POINTER); + AssertPtrReturn(pDesc, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + uint32_t const offHash = RT_ALIGN_32(pDesc->cbState, 8); + AssertReturn(pDesc->pfnNew || offHash, VERR_INVALID_PARAMETER); + AssertReturn(!pDesc->pfnNew || (pDesc->pfnFree && pDesc->pfnInit && pDesc->pfnClone), VERR_INVALID_PARAMETER); + PRTCRDIGESTINT pThis = (PRTCRDIGESTINT)RTMemAllocZ(RT_UOFFSETOF_DYN(RTCRDIGESTINT, abState[offHash + pDesc->cbHash])); + if (pThis) + { + if (pDesc->pfnNew) + pThis->pvState = pDesc->pfnNew(); + else + pThis->pvState = &pThis->abState[0]; + if (pThis->pvState) + { + pThis->u32Magic = RTCRDIGESTINT_MAGIC; + pThis->cRefs = 1; + pThis->offHash = offHash; + pThis->pDesc = pDesc; + pThis->uState = RTCRDIGEST_STATE_READY; + if (pDesc->pfnInit) + rc = pDesc->pfnInit(pThis->pvState, pvOpaque, false /*fReInit*/); + if (RT_SUCCESS(rc)) + { + *phDigest = pThis; + return rtCrDigestSuccessWithDigestWarnings(pDesc); + } + if (pDesc->pfnFree) + pDesc->pfnFree(pThis->pvState); + } + else + rc = VERR_NO_MEMORY; + pThis->u32Magic = 0; + RTMemFree(pThis); + } + else + rc = VERR_NO_MEMORY; + return rc; +} + + +RTDECL(int) RTCrDigestClone(PRTCRDIGEST phDigest, RTCRDIGEST hSrc) +{ + AssertPtrReturn(phDigest, VERR_INVALID_POINTER); + AssertPtrReturn(hSrc, VERR_INVALID_HANDLE); + AssertReturn(hSrc->u32Magic == RTCRDIGESTINT_MAGIC, VERR_INVALID_HANDLE); + + int rc = VINF_SUCCESS; + uint32_t const offHash = hSrc->offHash; + PRTCRDIGESTINT pThis = (PRTCRDIGESTINT)RTMemAllocZ(RT_UOFFSETOF_DYN(RTCRDIGESTINT, abState[offHash + hSrc->pDesc->cbHash])); + if (pThis) + { + if (hSrc->pDesc->pfnNew) + pThis->pvState = hSrc->pDesc->pfnNew(); + else + pThis->pvState = &pThis->abState[0]; + if (pThis->pvState) + { + pThis->u32Magic = RTCRDIGESTINT_MAGIC; + pThis->cRefs = 1; + pThis->offHash = offHash; + pThis->pDesc = hSrc->pDesc; + if (hSrc->pDesc->pfnClone) + rc = hSrc->pDesc->pfnClone(pThis->pvState, hSrc->pvState); + else + { + Assert(!hSrc->pDesc->pfnNew); + memcpy(pThis->pvState, hSrc->pvState, offHash); + } + memcpy(&pThis->abState[offHash], &hSrc->abState[offHash], hSrc->pDesc->cbHash); + pThis->uState = hSrc->uState; + + if (RT_SUCCESS(rc)) + { + *phDigest = pThis; + return rtCrDigestSuccessWithDigestWarnings(pThis->pDesc); + } + if (hSrc->pDesc->pfnFree) + hSrc->pDesc->pfnFree(pThis->pvState); + } + else + rc = VERR_NO_MEMORY; + pThis->u32Magic = 0; + RTMemFree(pThis); + } + else + rc = VERR_NO_MEMORY; + return rc; +} + + +RTDECL(int) RTCrDigestReset(RTCRDIGEST hDigest) +{ + PRTCRDIGESTINT pThis = hDigest; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRDIGESTINT_MAGIC, VERR_INVALID_HANDLE); + + pThis->cbConsumed = 0; + pThis->uState = RTCRDIGEST_STATE_READY; + + int rc = VINF_SUCCESS; + if (pThis->pDesc->pfnInit) + { + rc = pThis->pDesc->pfnInit(pThis->pvState, NULL, true /*fReInit*/); + if (RT_FAILURE(rc)) + pThis->uState = RTCRDIGEST_STATE_BUSTED; + RT_BZERO(&pThis->abState[pThis->offHash], pThis->pDesc->cbHash); + } + else + { + Assert(!pThis->pDesc->pfnNew); + RT_BZERO(pThis->pvState, pThis->offHash + pThis->pDesc->cbHash); + } + return rc; +} + + +RTDECL(uint32_t) RTCrDigestRetain(RTCRDIGEST hDigest) +{ + PRTCRDIGESTINT pThis = hDigest; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTCRDIGESTINT_MAGIC, UINT32_MAX); + + uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs); + Assert(cRefs < 64); + return cRefs; +} + + +RTDECL(uint32_t) RTCrDigestRelease(RTCRDIGEST hDigest) +{ + PRTCRDIGESTINT pThis = hDigest; + if (pThis == NIL_RTCRDIGEST) + return 0; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTCRDIGESTINT_MAGIC, UINT32_MAX); + + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + if (!cRefs) + { + pThis->u32Magic = ~RTCRDIGESTINT_MAGIC; + if (pThis->pDesc->pfnDelete) + pThis->pDesc->pfnDelete(pThis->pvState); + if (pThis->pDesc->pfnFree) + pThis->pDesc->pfnFree(pThis->pvState); + RTMemFree(pThis); + } + Assert(cRefs < 64); + return cRefs; +} + + +RTDECL(int) RTCrDigestUpdate(RTCRDIGEST hDigest, void const *pvData, size_t cbData) +{ + PRTCRDIGESTINT pThis = hDigest; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRDIGESTINT_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(pThis->uState == RTCRDIGEST_STATE_READY, VERR_INVALID_STATE); + + pThis->pDesc->pfnUpdate(pThis->pvState, pvData, cbData); + pThis->cbConsumed += cbData; + return VINF_SUCCESS; +} + + +RTDECL(int) RTCrDigestFinal(RTCRDIGEST hDigest, void *pvHash, size_t cbHash) +{ + PRTCRDIGESTINT pThis = hDigest; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRDIGESTINT_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(pThis->uState == RTCRDIGEST_STATE_READY || pThis->uState == RTCRDIGEST_STATE_FINAL, VERR_INVALID_STATE); + AssertPtrNullReturn(pvHash, VERR_INVALID_POINTER); + + /* + * Make sure the hash calculation is final. + */ + if (pThis->uState == RTCRDIGEST_STATE_READY) + { + pThis->pDesc->pfnFinal(pThis->pvState, &pThis->abState[pThis->offHash]); + pThis->uState = RTCRDIGEST_STATE_FINAL; + } + else + AssertReturn(pThis->uState == RTCRDIGEST_STATE_FINAL, VERR_INVALID_STATE); + + /* + * Copy out the hash if requested. + */ + if (cbHash > 0) + { + uint32_t cbNeeded = pThis->pDesc->cbHash; + if (pThis->pDesc->pfnGetHashSize) + cbNeeded = pThis->pDesc->pfnGetHashSize(pThis->pvState); + Assert(cbNeeded > 0); + + if (cbNeeded == cbHash) + memcpy(pvHash, &pThis->abState[pThis->offHash], cbNeeded); + else if (cbNeeded < cbHash) + { + memcpy(pvHash, &pThis->abState[pThis->offHash], cbNeeded); + memset((uint8_t *)pvHash + cbNeeded, 0, cbHash - cbNeeded); + return VINF_BUFFER_UNDERFLOW; + } + else + { + memcpy(pvHash, &pThis->abState[pThis->offHash], cbHash); + return VERR_BUFFER_OVERFLOW; + } + } + + return rtCrDigestSuccessWithDigestWarnings(pThis->pDesc); +} + + +RTDECL(bool) RTCrDigestMatch(RTCRDIGEST hDigest, void const *pvHash, size_t cbHash) +{ + PRTCRDIGESTINT pThis = hDigest; + + int rc = RTCrDigestFinal(pThis, NULL, 0); + AssertRCReturn(rc, false); + + AssertPtrReturn(pvHash, false); + return pThis->pDesc->cbHash == cbHash + && !memcmp(&pThis->abState[pThis->offHash], pvHash, cbHash); +} + + +RTDECL(uint8_t const *) RTCrDigestGetHash(RTCRDIGEST hDigest) +{ + PRTCRDIGESTINT pThis = hDigest; + AssertPtrReturn(pThis, NULL); + AssertReturn(pThis->u32Magic == RTCRDIGESTINT_MAGIC, NULL); + + int rc = RTCrDigestFinal(pThis, NULL, 0); + AssertRCReturn(rc, NULL); + + return &pThis->abState[pThis->offHash]; +} + + +RTDECL(uint32_t) RTCrDigestGetHashSize(RTCRDIGEST hDigest) +{ + PRTCRDIGESTINT pThis = hDigest; + AssertPtrReturn(pThis, 0); + AssertReturn(pThis->u32Magic == RTCRDIGESTINT_MAGIC, 0); + if (pThis->pDesc->pfnGetHashSize) + { + uint32_t cbHash = pThis->pDesc->pfnGetHashSize(pThis->pvState); + Assert(cbHash <= pThis->pDesc->cbHash); + return cbHash; + } + return pThis->pDesc->cbHash; +} + + +RTDECL(uint64_t) RTCrDigestGetConsumedSize(RTCRDIGEST hDigest) +{ + PRTCRDIGESTINT pThis = hDigest; + AssertPtrReturn(pThis, 0); + AssertReturn(pThis->u32Magic == RTCRDIGESTINT_MAGIC, 0); + return pThis->cbConsumed; +} + + +RTDECL(bool) RTCrDigestIsFinalized(RTCRDIGEST hDigest) +{ + PRTCRDIGESTINT pThis = hDigest; + AssertPtrReturn(pThis, false); + AssertReturn(pThis->u32Magic == RTCRDIGESTINT_MAGIC, false); + return pThis->uState == RTCRDIGEST_STATE_FINAL; +} + + +RTDECL(RTDIGESTTYPE) RTCrDigestGetType(RTCRDIGEST hDigest) +{ + PRTCRDIGESTINT pThis = hDigest; + AssertPtrReturn(pThis, RTDIGESTTYPE_INVALID); + AssertReturn(pThis->u32Magic == RTCRDIGESTINT_MAGIC, RTDIGESTTYPE_INVALID); + + RTDIGESTTYPE enmType = pThis->pDesc->enmType; + if (pThis->pDesc->pfnGetDigestType) + enmType = pThis->pDesc->pfnGetDigestType(pThis->pvState); + return enmType; +} + + +RTDECL(const char *) RTCrDigestGetAlgorithmOid(RTCRDIGEST hDigest) +{ + return RTCrDigestTypeToAlgorithmOid(RTCrDigestGetType(hDigest)); +} + + +RTDECL(uint32_t) RTCrDigestGetFlags(RTCRDIGEST hDigest) +{ + PRTCRDIGESTINT pThis = hDigest; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTCRDIGESTINT_MAGIC, UINT32_MAX); + return pThis->pDesc->fFlags; +} + + +RTDECL(const char *) RTCrDigestTypeToAlgorithmOid(RTDIGESTTYPE enmDigestType) +{ + switch (enmDigestType) + { + case RTDIGESTTYPE_MD2: return RTCRX509ALGORITHMIDENTIFIERID_MD2; + case RTDIGESTTYPE_MD4: return RTCRX509ALGORITHMIDENTIFIERID_MD4; + case RTDIGESTTYPE_MD5: return RTCRX509ALGORITHMIDENTIFIERID_MD5; + case RTDIGESTTYPE_SHA1: return RTCRX509ALGORITHMIDENTIFIERID_SHA1; + case RTDIGESTTYPE_SHA224: return RTCRX509ALGORITHMIDENTIFIERID_SHA224; + case RTDIGESTTYPE_SHA256: return RTCRX509ALGORITHMIDENTIFIERID_SHA256; + case RTDIGESTTYPE_SHA384: return RTCRX509ALGORITHMIDENTIFIERID_SHA384; + case RTDIGESTTYPE_SHA512: return RTCRX509ALGORITHMIDENTIFIERID_SHA512; + case RTDIGESTTYPE_SHA512T224: return RTCRX509ALGORITHMIDENTIFIERID_SHA512T224; + case RTDIGESTTYPE_SHA512T256: return RTCRX509ALGORITHMIDENTIFIERID_SHA512T256; + case RTDIGESTTYPE_SHA3_224: return RTCRX509ALGORITHMIDENTIFIERID_SHA3_224; + case RTDIGESTTYPE_SHA3_256: return RTCRX509ALGORITHMIDENTIFIERID_SHA3_256; + case RTDIGESTTYPE_SHA3_384: return RTCRX509ALGORITHMIDENTIFIERID_SHA3_384; + case RTDIGESTTYPE_SHA3_512: return RTCRX509ALGORITHMIDENTIFIERID_SHA3_512; + default: return NULL; + } +} + + +RTDECL(const char *) RTCrDigestTypeToName(RTDIGESTTYPE enmDigestType) +{ + switch (enmDigestType) + { + case RTDIGESTTYPE_CRC32: return "CRC32"; + case RTDIGESTTYPE_CRC64: return "CRC64"; + case RTDIGESTTYPE_MD2: return "MD2"; + case RTDIGESTTYPE_MD4: return "MD4"; + case RTDIGESTTYPE_MD5: return "MD5"; + case RTDIGESTTYPE_SHA1: return "SHA-1"; + case RTDIGESTTYPE_SHA224: return "SHA-224"; + case RTDIGESTTYPE_SHA256: return "SHA-256"; + case RTDIGESTTYPE_SHA384: return "SHA-384"; + case RTDIGESTTYPE_SHA512: return "SHA-512"; + case RTDIGESTTYPE_SHA512T224: return "SHA-512/224"; + case RTDIGESTTYPE_SHA512T256: return "SHA-512/256"; + case RTDIGESTTYPE_SHA3_224: return "SHA3-224"; + case RTDIGESTTYPE_SHA3_256: return "SHA3-256"; + case RTDIGESTTYPE_SHA3_384: return "SHA3-384"; + case RTDIGESTTYPE_SHA3_512: return "SHA3-512"; + default: return NULL; + } +} + + +RTDECL(uint32_t) RTCrDigestTypeToHashSize(RTDIGESTTYPE enmDigestType) +{ + switch (enmDigestType) + { + case RTDIGESTTYPE_CRC32: return 32 / 8; + case RTDIGESTTYPE_CRC64: return 64 / 8; + case RTDIGESTTYPE_MD2: return 128 / 8; + case RTDIGESTTYPE_MD4: return 128 / 8; + case RTDIGESTTYPE_MD5: return 128 / 8; + case RTDIGESTTYPE_SHA1: return 160 / 8; + case RTDIGESTTYPE_SHA224: return 224 / 8; + case RTDIGESTTYPE_SHA256: return 256 / 8; + case RTDIGESTTYPE_SHA384: return 384 / 8; + case RTDIGESTTYPE_SHA512: return 512 / 8; + case RTDIGESTTYPE_SHA512T224: return 224 / 8; + case RTDIGESTTYPE_SHA512T256: return 256 / 8; + case RTDIGESTTYPE_SHA3_224: return 224 / 8; + case RTDIGESTTYPE_SHA3_256: return 256 / 8; + case RTDIGESTTYPE_SHA3_384: return 384 / 8; + case RTDIGESTTYPE_SHA3_512: return 512 / 8; + default: + AssertFailed(); + return 0; + } +} + diff --git a/src/VBox/Runtime/common/crypto/digest-vfs.cpp b/src/VBox/Runtime/common/crypto/digest-vfs.cpp new file mode 100644 index 00000000..f0636234 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/digest-vfs.cpp @@ -0,0 +1,80 @@ +/* $Id: digest-vfs.cpp $ */ +/** @file + * IPRT - Crypto - Cryptographic Hash / Message Digest API, VFS related interfaces + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/digest.h> + +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/vfs.h> + + +RTDECL(int) RTCrDigestUpdateFromVfsFile(RTCRDIGEST hDigest, RTVFSFILE hVfsFile, bool fRewindFile) +{ + int rc; + if (fRewindFile) + rc = RTVfsFileSeek(hVfsFile, 0, RTFILE_SEEK_BEGIN, NULL); + else + rc = VINF_SUCCESS; + if (RT_SUCCESS(rc)) + { + for (;;) + { + char abBuf[_16K]; + size_t cbRead; + rc = RTVfsFileRead(hVfsFile, abBuf, sizeof(abBuf), &cbRead); + if (RT_SUCCESS(rc)) + { + bool const fEof = rc == VINF_EOF; + rc = RTCrDigestUpdate(hDigest, abBuf, cbRead); + if (fEof || RT_FAILURE(rc)) + break; + } + else + { + Assert(rc != VERR_EOF); + break; + } + } + } + return rc; +} + diff --git a/src/VBox/Runtime/common/crypto/iprt-openssl.cpp b/src/VBox/Runtime/common/crypto/iprt-openssl.cpp new file mode 100644 index 00000000..9eabd1e6 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/iprt-openssl.cpp @@ -0,0 +1,202 @@ +/* $Id: iprt-openssl.cpp $ */ +/** @file + * IPRT - Crypto - OpenSSL Helpers. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" + +#ifdef IPRT_WITH_OPENSSL /* Whole file. */ +# include <iprt/err.h> +# include <iprt/string.h> +# include <iprt/mem.h> +# include <iprt/asn1.h> +# include <iprt/crypto/digest.h> +# include <iprt/crypto/pkcs7.h> +# include <iprt/crypto/spc.h> + +# include "internal/iprt-openssl.h" +# include "internal/openssl-pre.h" +# include <openssl/x509.h> +# include <openssl/err.h> +# include "internal/openssl-post.h" + + +DECLHIDDEN(void) rtCrOpenSslInit(void) +{ + static bool s_fOssInitalized; + if (!s_fOssInitalized) + { + OpenSSL_add_all_algorithms(); + ERR_load_ERR_strings(); + ERR_load_crypto_strings(); + + /* Add some OIDs we might possibly want to use. */ + static struct { const char *pszOid, *pszDesc; } const s_aOids[] = + { + { RTCRSPC_PE_IMAGE_HASHES_V1_OID, "Ms-SpcPeImagePageHashesV1" }, + { RTCRSPC_PE_IMAGE_HASHES_V2_OID, "Ms-SpcPeImagePageHashesV2" }, + { RTCRSPC_STMT_TYPE_INDIVIDUAL_CODE_SIGNING, "Ms-SpcIndividualCodeSigning" }, + { RTCRSPCPEIMAGEDATA_OID, "Ms-SpcPeImageData" }, + { RTCRSPCINDIRECTDATACONTENT_OID, "Ms-SpcIndirectDataContext" }, + { RTCR_PKCS9_ID_MS_TIMESTAMP, "Ms-CounterSign" }, + { RTCR_PKCS9_ID_MS_NESTED_SIGNATURE, "Ms-SpcNestedSignature" }, + { RTCR_PKCS9_ID_MS_STATEMENT_TYPE, "Ms-SpcStatementType" }, + { RTCR_PKCS9_ID_MS_SP_OPUS_INFO, "Ms-SpcOpusInfo" }, + { "1.3.6.1.4.1.311.3.2.1", "Ms-SpcTimeStampRequest" }, /** @todo define */ + { "1.3.6.1.4.1.311.10.1", "Ms-CertTrustList" }, /** @todo define */ + }; + for (unsigned i = 0; i < RT_ELEMENTS(s_aOids); i++) + OBJ_create(s_aOids[i].pszOid, s_aOids[i].pszDesc, s_aOids[i].pszDesc); + + s_fOssInitalized = true; + } +} + + +DECLHIDDEN(int) rtCrOpenSslErrInfoCallback(const char *pach, size_t cch, void *pvUser) +{ + PRTERRINFO pErrInfo = (PRTERRINFO)pvUser; + size_t cchAlready = pErrInfo->fFlags & RTERRINFO_FLAGS_SET ? strlen(pErrInfo->pszMsg) : 0; + if (cchAlready + 1 < pErrInfo->cbMsg) + RTStrCopyEx(pErrInfo->pszMsg + cchAlready, pErrInfo->cbMsg - cchAlready, pach, cch); + return -1; +} + + +DECLHIDDEN(int) rtCrOpenSslConvertX509Cert(void **ppvOsslCert, PCRTCRX509CERTIFICATE pCert, PRTERRINFO pErrInfo) +{ + const unsigned char *pabEncoded; + uint32_t cbEncoded; + void *pvFree; + int rc = RTAsn1EncodeQueryRawBits(RTCrX509Certificate_GetAsn1Core(pCert), + (const uint8_t **)&pabEncoded, &cbEncoded, &pvFree, pErrInfo); + if (RT_SUCCESS(rc)) + { + X509 *pOsslCert = NULL; + X509 *pOsslCertRet = d2i_X509(&pOsslCert, &pabEncoded, cbEncoded); + RTMemTmpFree(pvFree); + if (pOsslCert != NULL && pOsslCertRet == pOsslCert) + { + *ppvOsslCert = pOsslCert; + return VINF_SUCCESS; + } + rc = RTErrInfoSet(pErrInfo, VERR_CR_X509_OSSL_D2I_FAILED, "d2i_X509"); + + } + *ppvOsslCert = NULL; + return rc; +} + + +DECLHIDDEN(void) rtCrOpenSslFreeConvertedX509Cert(void *pvOsslCert) +{ + X509_free((X509 *)pvOsslCert); +} + + +DECLHIDDEN(int) rtCrOpenSslAddX509CertToStack(void *pvOsslStack, PCRTCRX509CERTIFICATE pCert, PRTERRINFO pErrInfo) +{ + X509 *pOsslCert = NULL; + int rc = rtCrOpenSslConvertX509Cert((void **)&pOsslCert, pCert, pErrInfo); + if (RT_SUCCESS(rc)) + { + if (sk_X509_push((STACK_OF(X509) *)pvOsslStack, pOsslCert)) + rc = VINF_SUCCESS; + else + { + rtCrOpenSslFreeConvertedX509Cert(pOsslCert); + rc = RTErrInfoSet(pErrInfo, VERR_NO_MEMORY, "sk_X509_push"); + } + } + return rc; +} + + +DECLHIDDEN(const void /*EVP_MD*/ *) rtCrOpenSslConvertDigestType(RTDIGESTTYPE enmDigestType, PRTERRINFO pErrInfo) +{ + const char *pszAlgoObjId = RTCrDigestTypeToAlgorithmOid(enmDigestType); + AssertReturnStmt(pszAlgoObjId, RTErrInfoSetF(pErrInfo, VERR_INVALID_PARAMETER, "Invalid type: %d", enmDigestType), NULL); + + int iAlgoNid = OBJ_txt2nid(pszAlgoObjId); + AssertReturnStmt(iAlgoNid != NID_undef, + RTErrInfoSetF(pErrInfo, VERR_CR_DIGEST_OSSL_DIGEST_INIT_ERROR, + "OpenSSL does not know: %s (%s)", pszAlgoObjId, RTCrDigestTypeToName(enmDigestType)), + NULL); + + const char *pszAlgoSn = OBJ_nid2sn(iAlgoNid); + const EVP_MD *pEvpMdType = EVP_get_digestbyname(pszAlgoSn); + AssertReturnStmt(pEvpMdType, + RTErrInfoSetF(pErrInfo, VERR_CR_DIGEST_OSSL_DIGEST_INIT_ERROR, "OpenSSL/EVP does not know: %d (%s; %s; %s)", + iAlgoNid, pszAlgoSn, pszAlgoSn, RTCrDigestTypeToName(enmDigestType)), + NULL); + + return pEvpMdType; +} + +DECLHIDDEN(int) rtCrOpenSslConvertPkcs7Attribute(void **ppvOsslAttrib, PCRTCRPKCS7ATTRIBUTE pAttrib, PRTERRINFO pErrInfo) +{ + const unsigned char *pabEncoded; + uint32_t cbEncoded; + void *pvFree; + int rc = RTAsn1EncodeQueryRawBits(RTCrPkcs7Attribute_GetAsn1Core(pAttrib), + (const uint8_t **)&pabEncoded, &cbEncoded, &pvFree, pErrInfo); + if (RT_SUCCESS(rc)) + { + X509_ATTRIBUTE *pOsslAttrib = NULL; + X509_ATTRIBUTE *pOsslAttribRet = d2i_X509_ATTRIBUTE(&pOsslAttrib, &pabEncoded, cbEncoded); + RTMemTmpFree(pvFree); + if (pOsslAttrib != NULL && pOsslAttribRet == pOsslAttrib) + { + *ppvOsslAttrib = pOsslAttrib; + return VINF_SUCCESS; + } + rc = RTErrInfoSet(pErrInfo, VERR_CR_X509_OSSL_D2I_FAILED, "d2i_X509_ATTRIBUTE"); + } + *ppvOsslAttrib = NULL; + return rc; +} + + +DECLHIDDEN(void) rtCrOpenSslFreeConvertedPkcs7Attribute(void *pvOsslAttrib) +{ + X509_ATTRIBUTE_free((X509_ATTRIBUTE *)pvOsslAttrib); +} + + +#endif /* IPRT_WITH_OPENSSL */ + diff --git a/src/VBox/Runtime/common/crypto/key-create-rsa-openssl.cpp b/src/VBox/Runtime/common/crypto/key-create-rsa-openssl.cpp new file mode 100644 index 00000000..7f2b939f --- /dev/null +++ b/src/VBox/Runtime/common/crypto/key-create-rsa-openssl.cpp @@ -0,0 +1,104 @@ +/* $Id: key-create-rsa-openssl.cpp $ */ +/** @file + * IPRT - Crypto - RSA Key Creation using OpenSSL. + */ + +/* + * Copyright (C) 2018-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/key.h> + +#ifdef IPRT_WITH_OPENSSL /* Whole file. */ +# include <iprt/err.h> +# include <iprt/string.h> + +# include "internal/iprt-openssl.h" +# include "internal/openssl-pre.h" +# include <openssl/rsa.h> +# include <openssl/err.h> +# include "internal/openssl-post.h" + +# include "key-internal.h" + + + +RTDECL(int) RTCrKeyCreateNewRsa(PRTCRKEY phKey, uint32_t cBits, uint32_t uPubExp, uint32_t fFlags) +{ + AssertPtrReturn(phKey, VERR_INVALID_POINTER); + AssertMsgReturn(cBits >= 128 && cBits <= _64K, ("cBits=%u\n", cBits), VERR_OUT_OF_RANGE); + AssertReturn(uPubExp > 0, VERR_OUT_OF_RANGE); + AssertReturn(!fFlags, VERR_INVALID_FLAGS); + + rtCrOpenSslInit(); + + /* + * Do the key generation first. + */ + int rc = VERR_NO_MEMORY; + RSA *pRsa = RSA_new(); + if (pRsa) + { + BIGNUM *pPubExp = BN_new(); + if (pPubExp) + { + if (BN_set_word(pPubExp, uPubExp) != 0) + { + if (RSA_generate_key_ex(pRsa, cBits, pPubExp, NULL)) + { + /* + * Create a IPRT key for it by encoding it as a private key. + */ + unsigned char *pbRsaPrivateKey = NULL; + int cbRsaPrivateKey = i2d_RSAPrivateKey(pRsa, &pbRsaPrivateKey); + if (cbRsaPrivateKey > 0) + { + rc = rtCrKeyCreateRsaPrivate(phKey, pbRsaPrivateKey, cbRsaPrivateKey, NULL, NULL); + OPENSSL_free(pbRsaPrivateKey); + } + /* else: VERR_NO_MEMORY */ + } + else + rc = VERR_CR_KEY_GEN_FAILED_RSA; + } + BN_free(pPubExp); + } + RSA_free(pRsa); + } + return rc; +} + +#endif /* IPRT_WITH_OPENSSL */ + diff --git a/src/VBox/Runtime/common/crypto/key-file.cpp b/src/VBox/Runtime/common/crypto/key-file.cpp new file mode 100644 index 00000000..d05e224f --- /dev/null +++ b/src/VBox/Runtime/common/crypto/key-file.cpp @@ -0,0 +1,528 @@ +/* $Id: key-file.cpp $ */ +/** @file + * IPRT - Crypto - Cryptographic Keys, File I/O. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/key.h> + +#include <iprt/alloca.h> +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/memsafer.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/crypto/rsa.h> +#include <iprt/crypto/pkix.h> +#include <iprt/crypto/x509.h> + +#include "internal/magics.h" +#include "key-internal.h" + +#ifdef IPRT_WITH_OPENSSL +# include "internal/iprt-openssl.h" +# include "internal/openssl-pre.h" +# include <openssl/evp.h> +# include "internal/openssl-post.h" +# ifndef OPENSSL_VERSION_NUMBER +# error "Missing OPENSSL_VERSION_NUMBER!" +# endif +#endif + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +/** RSA public key marker words. */ +static RTCRPEMMARKERWORD const g_aWords_RsaPublicKey[] = +{ { RT_STR_TUPLE("RSA") }, { RT_STR_TUPLE("PUBLIC") }, { RT_STR_TUPLE("KEY") } }; +/** Generic public key marker words. */ +static RTCRPEMMARKERWORD const g_aWords_PublicKey[] = +{ { RT_STR_TUPLE("PUBLIC") }, { RT_STR_TUPLE("KEY") } }; + +/** Public key markers. */ +RT_DECL_DATA_CONST(RTCRPEMMARKER const) g_aRTCrKeyPublicMarkers[] = +{ + { g_aWords_RsaPublicKey, RT_ELEMENTS(g_aWords_RsaPublicKey) }, + { g_aWords_PublicKey, RT_ELEMENTS(g_aWords_PublicKey) }, +}; +/** Number of entries in g_aRTCrKeyPublicMarkers. */ +RT_DECL_DATA_CONST(uint32_t const) g_cRTCrKeyPublicMarkers = RT_ELEMENTS(g_aRTCrKeyPublicMarkers); + + +/** RSA private key marker words. */ +static RTCRPEMMARKERWORD const g_aWords_RsaPrivateKey[] = +{ { RT_STR_TUPLE("RSA") }, { RT_STR_TUPLE("PRIVATE") }, { RT_STR_TUPLE("KEY") } }; +/** Generic encrypted private key marker words. */ +static RTCRPEMMARKERWORD const g_aWords_EncryptedPrivateKey[] = +{ { RT_STR_TUPLE("ENCRYPTED") }, { RT_STR_TUPLE("PRIVATE") }, { RT_STR_TUPLE("KEY") } }; +/** Generic private key marker words. */ +static RTCRPEMMARKERWORD const g_aWords_PrivateKey[] = +{ { RT_STR_TUPLE("PRIVATE") }, { RT_STR_TUPLE("KEY") } }; + +/** Private key markers. */ +RT_DECL_DATA_CONST(RTCRPEMMARKER const) g_aRTCrKeyPrivateMarkers[] = +{ + { g_aWords_RsaPrivateKey, RT_ELEMENTS(g_aWords_RsaPrivateKey) }, + { g_aWords_EncryptedPrivateKey, RT_ELEMENTS(g_aWords_EncryptedPrivateKey) }, + { g_aWords_PrivateKey, RT_ELEMENTS(g_aWords_PrivateKey) }, +}; +/** Number of entries in g_aRTCrKeyPrivateMarkers. */ +RT_DECL_DATA_CONST(uint32_t const) g_cRTCrKeyPrivateMarkers = RT_ELEMENTS(g_aRTCrKeyPrivateMarkers); + + +/** Private and public key markers. */ +RT_DECL_DATA_CONST(RTCRPEMMARKER const) g_aRTCrKeyAllMarkers[] = +{ + { g_aWords_RsaPublicKey, RT_ELEMENTS(g_aWords_RsaPublicKey) }, + { g_aWords_PublicKey, RT_ELEMENTS(g_aWords_PublicKey) }, + { g_aWords_RsaPrivateKey, RT_ELEMENTS(g_aWords_RsaPrivateKey) }, + { g_aWords_PrivateKey, RT_ELEMENTS(g_aWords_PrivateKey) }, +}; +/** Number of entries in g_aRTCrKeyAllMarkers. */ +RT_DECL_DATA_CONST(uint32_t const) g_cRTCrKeyAllMarkers = RT_ELEMENTS(g_aRTCrKeyAllMarkers); + + +/** + * Decrypts a PEM message. + * + * @returns IPRT status code + * @param pszDekInfo The decryption info. See RFC-1421 section 4.6.1.3 + * as well as RFC-1423). + * @param pszPassword The password to use to decrypt the key text. + * @param pbEncrypted The encrypted key text. + * @param cbEncrypted The size of the encrypted text. + * @param ppbDecrypted Where to return the decrypted message. Free using RTMemSaferFree. + * @param pcbDecrypted Where to return the length of the decrypted message. + * @param pcbDecryptedAlloced Where to return the allocation size. + * @param pErrInfo Where to return additional error information. + */ +static int rtCrKeyDecryptPemMessage(const char *pszDekInfo, const char *pszPassword, uint8_t *pbEncrypted, size_t cbEncrypted, + uint8_t **ppbDecrypted, size_t *pcbDecrypted, size_t *pcbDecryptedAlloced, PRTERRINFO pErrInfo) +{ + /* + * Initialize return values. + */ + *ppbDecrypted = NULL; + *pcbDecrypted = 0; + *pcbDecryptedAlloced = 0; + + /* + * Parse the DEK-Info. + */ + if (!pszDekInfo) + return VERR_CR_KEY_NO_DEK_INFO; + + /* Find the end of the algorithm */ + const char *pszParams = strchr(pszDekInfo, ','); + if (!pszParams) + pszParams = strchr(pszDekInfo, '\0'); + size_t cchAlgo = pszParams - pszDekInfo; + while (cchAlgo > 0 && RT_C_IS_SPACE(pszDekInfo[cchAlgo - 1])) + cchAlgo--; + + /* Copy it out and zero terminating it. */ + char szAlgo[256]; + if (cchAlgo >= sizeof(szAlgo)) + return RTErrInfoSetF(pErrInfo, VERR_CR_KEY_DEK_INFO_TOO_LONG, "Algorithms list is too long (%s)", pszDekInfo); + memcpy(szAlgo, pszDekInfo, cchAlgo); + szAlgo[cchAlgo] = '\0'; + + /* Parameters. */ + pszParams = RTStrStripL(*pszParams == ',' ? pszParams + 1 : pszParams); + size_t const cchParams = strlen(pszParams); + + /* + * Do we support the cihper? + */ +#ifdef IPRT_WITH_OPENSSL /** @todo abstract encryption & decryption. */ + const EVP_CIPHER *pCipher = EVP_get_cipherbyname(szAlgo); + if (!pCipher) + return RTErrInfoSetF(pErrInfo, VERR_CR_KEY_UNSUPPORTED_CIPHER, "Unknown key cipher: %s (params: %s)", szAlgo, pszParams); + + /* Decode the initialization vector if one is required. */ + uint8_t *pbInitVector = NULL; + int const cbInitVector = EVP_CIPHER_iv_length(pCipher); + if (cbInitVector > 0) + { + if (*pszParams == '\0') + return RTErrInfoSetF(pErrInfo, VERR_CR_KEY_MISSING_CIPHER_PARAMS, + "Cipher '%s' expected %u bytes initialization vector, none found", cbInitVector, szAlgo); + if ((size_t)cbInitVector > cchParams / 2) + return RTErrInfoSetF(pErrInfo, VERR_CR_KEY_TOO_SHORT_CIPHER_IV, + "Too short initialization vector for '%s', expected %u chars found only %u: %s", + szAlgo, cbInitVector * 2, cchParams, pszParams); + pbInitVector = (uint8_t *)alloca(cbInitVector); + int rc = RTStrConvertHexBytes(pszParams, pbInitVector, cbInitVector, 0 /*fFlags*/); + if ( RT_FAILURE(rc) + && rc != VERR_BUFFER_OVERFLOW /* openssl ignores this condition */) + return RTErrInfoSetF(pErrInfo, VERR_CR_KEY_MALFORMED_CIPHER_IV, + "Malformed initialization vector for '%s': %s (rc=%Rrc)", szAlgo, pszParams, rc); + } + else if (*pszParams != '\0') + return RTErrInfoSetF(pErrInfo, VERR_CR_KEY_UNEXPECTED_CIPHER_PARAMS, + "Cipher '%s' expected no parameters, found: %s", szAlgo, pszParams); + + /* + * Do we have a password? If so try decrypt the key. + */ + if (!pszPassword) + return VERR_CR_KEY_ENCRYPTED; + + unsigned char abKey[EVP_MAX_KEY_LENGTH * 2]; + int cbKey = EVP_BytesToKey(pCipher, EVP_md5(), pbInitVector, (unsigned char const *)pszPassword, (int)strlen(pszPassword), + 1, abKey, NULL); + if (!cbKey) + return RTErrInfoSetF(pErrInfo, VERR_CR_KEY_PASSWORD_ENCODING, "EVP_BytesToKey failed to encode password"); + + EVP_CIPHER_CTX *pCipherCtx = EVP_CIPHER_CTX_new(); + if (!pCipherCtx) + return VERR_NO_MEMORY; + + int rc; + if (EVP_DecryptInit_ex(pCipherCtx, pCipher, NULL /*pEngine*/, abKey, pbInitVector)) + { + size_t cbDecryptedAlloced = cbEncrypted; + int cbDecrypted = (int)cbDecryptedAlloced; + uint8_t *pbDecrypted = (uint8_t *)RTMemSaferAllocZ(cbDecryptedAlloced); + if (pbDecrypted) + { + if (EVP_DecryptUpdate(pCipherCtx, pbDecrypted, &cbDecrypted, pbEncrypted, (int)cbEncrypted)) + { + int cbFinal = (int)cbDecryptedAlloced - cbDecrypted; + if (EVP_DecryptFinal_ex(pCipherCtx, &pbDecrypted[cbDecrypted], &cbFinal)) + { + cbDecrypted += cbFinal; + Assert((size_t)cbDecrypted <= cbDecryptedAlloced); + + /* + * Done! Just set the return values. + */ + *pcbDecrypted = cbDecrypted; + *pcbDecryptedAlloced = cbDecryptedAlloced; + *ppbDecrypted = pbDecrypted; + pbDecrypted = NULL; + rc = VINF_CR_KEY_WAS_DECRYPTED; + } + else + rc = RTErrInfoSetF(pErrInfo, VERR_CR_KEY_DECRYPTION_FAILED, + "Incorrect password? EVP_DecryptFinal_ex failed for %s", pszDekInfo); + } + else + rc = RTErrInfoSetF(pErrInfo, VERR_CR_KEY_DECRYPTION_FAILED, + "Incorrect password? EVP_DecryptUpdate failed for %s", pszDekInfo); + if (pbDecrypted) + RTMemSaferFree(pbDecrypted, cbDecryptedAlloced); + } + else + rc = VERR_NO_MEMORY; + } + else + rc = RTErrInfoSetF(pErrInfo, VERR_CR_KEY_OSSL_DECRYPT_INIT_ERROR, "EVP_DecryptInit_ex failed for %s", pszDekInfo); + EVP_CIPHER_CTX_free(pCipherCtx); + return rc; +#else + RT_NOREF(pbEncrypted, cbEncrypted, pszPassword, pErrInfo, cchParams); + return VERR_CR_KEY_DECRYPTION_NOT_SUPPORTED; +#endif +} + + +RTDECL(int) RTCrKeyCreateFromPemSection(PRTCRKEY phKey, PCRTCRPEMSECTION pSection, uint32_t fFlags, const char *pszPassword, + PRTERRINFO pErrInfo, const char *pszErrorTag) +{ + AssertReturn(!(fFlags & (~RTCRKEYFROM_F_VALID_MASK | RTCRKEYFROM_F_ONLY_PEM)), VERR_INVALID_FLAGS); + + AssertPtrReturn(phKey, VERR_INVALID_POINTER); + *phKey = NIL_RTCRKEY; + AssertPtrReturn(pSection, VERR_INVALID_POINTER); + NOREF(pszPassword); + + /* + * If the source is PEM section, try identify the format from the markers. + */ + enum + { + kKeyFormat_Unknown = 0, + kKeyFormat_RsaPrivateKey, + kKeyFormat_RsaEncryptedPrivateKey, + kKeyFormat_RsaPublicKey, + kKeyFormat_SubjectPublicKeyInfo, + kKeyFormat_PrivateKeyInfo, + kKeyFormat_EncryptedPrivateKeyInfo + } enmFormat = kKeyFormat_Unknown; + const char *pszDekInfo = NULL; + PCRTCRPEMMARKER pMarker = pSection->pMarker; + if (pMarker) + { + if ( pMarker->cWords == 3 + && strcmp(pMarker->paWords[0].pszWord, "RSA") == 0 + && strcmp(pMarker->paWords[2].pszWord, "KEY") == 0) + { + if (strcmp(pMarker->paWords[1].pszWord, "PUBLIC") == 0) + enmFormat = kKeyFormat_RsaPublicKey; + else if (strcmp(pMarker->paWords[1].pszWord, "PRIVATE") == 0) + { + enmFormat = kKeyFormat_RsaPrivateKey; + + /* RSA PRIVATE KEY encryption is advertised thru PEM header fields. + We need the DEK field to decrypt the message (see RFC-1421 4.6.1.3). */ + for (PCRTCRPEMFIELD pField = pSection->pFieldHead; pField; pField = pField->pNext) + { + if ( pField->cchName == sizeof("Proc-Type") - 1 + && pField->cchValue >= sizeof("4,ENCRYPTED") - 1 + && memcmp(pField->szName, RT_STR_TUPLE("Proc-Type")) == 0) + { + const char *pszValue = pField->pszValue; + if (*pszValue == '4') + { + do + pszValue++; + while (RT_C_IS_SPACE(*pszValue) || RT_C_IS_PUNCT(*pszValue)); + if (strcmp(pszValue, "ENCRYPTED") == 0) + enmFormat = kKeyFormat_RsaEncryptedPrivateKey; + } + } + else if ( pField->cchName == sizeof("DEK-Info") - 1 + && pField->cchValue > 0 + && !pszDekInfo) + pszDekInfo = pField->pszValue; + } + } + else + AssertFailed(); + } + else if ( pMarker->cWords == 2 + && strcmp(pMarker->paWords[1].pszWord, "KEY") == 0) + { + if (strcmp(pMarker->paWords[0].pszWord, "PUBLIC") == 0) + enmFormat = kKeyFormat_SubjectPublicKeyInfo; + else if (strcmp(pMarker->paWords[0].pszWord, "PRIVATE") == 0) + enmFormat = kKeyFormat_PrivateKeyInfo; + else + AssertFailed(); + } + else if ( pMarker->cWords == 3 + && strcmp(pMarker->paWords[0].pszWord, "ENCRYPTED") == 0 + && strcmp(pMarker->paWords[1].pszWord, "PRIVATE") == 0 + && strcmp(pMarker->paWords[2].pszWord, "KEY") == 0) + enmFormat = kKeyFormat_EncryptedPrivateKeyInfo; + else + AssertFailed(); + } + + /* + * Try guess the format from the binary data if needed. + */ + RTASN1CURSORPRIMARY PrimaryCursor; + if ( enmFormat == kKeyFormat_Unknown + && pSection->cbData > 10) + { + RTAsn1CursorInitPrimary(&PrimaryCursor, pSection->pbData, (uint32_t)pSection->cbData, + pErrInfo, &g_RTAsn1DefaultAllocator, RTASN1CURSOR_FLAGS_DER, "probing/0"); + + /* + * First the must be a sequence. + */ + RTASN1CORE Tag; + int rc = RTAsn1CursorReadHdr(&PrimaryCursor.Cursor, &Tag, "#1"); + if (RT_SUCCESS(rc) && Tag.uTag == ASN1_TAG_SEQUENCE) + { + RTASN1CURSOR Cursor2; + RTAsn1CursorInitSubFromCore(&PrimaryCursor.Cursor, &Tag, &Cursor2, "probing/1"); + rc = RTAsn1CursorReadHdr(&Cursor2, &Tag, "#2"); + + /* + * SEQUENCE SubjectPublicKeyInfo.Algorithm? + */ + if (RT_SUCCESS(rc) && Tag.uTag == ASN1_TAG_SEQUENCE) + { + RTASN1CURSOR Cursor3; + RTAsn1CursorInitSubFromCore(&Cursor2, &Tag, &Cursor3, "probing/2"); + rc = RTAsn1CursorReadHdr(&Cursor3, &Tag, "#3"); + + /* SEQUENCE SubjectPublicKeyInfo.Algorithm.Algorithm? */ + if (RT_SUCCESS(rc) && Tag.uTag == ASN1_TAG_OID) + enmFormat = kKeyFormat_SubjectPublicKeyInfo; + } + /* + * INTEGER PrivateKeyInfo.Version? + * INTEGER RsaPublicKey.Modulus? + * INTEGER RsaPrivateKey.Version? + */ + else if (RT_SUCCESS(rc) && Tag.uTag == ASN1_TAG_INTEGER) + { + rc = RTAsn1CursorReadHdr(RTAsn1CursorSkip(&Cursor2, Tag.cb), &Tag, "#4"); + + /* OBJECT PrivateKeyInfo.privateKeyAlgorithm? */ + if (RT_SUCCESS(rc) && Tag.uTag == ASN1_TAG_OID) + enmFormat = kKeyFormat_PrivateKeyInfo; + /* INTEGER RsaPublicKey.PublicExponent? + INTEGER RsaPrivateKey.Modulus? */ + else if (RT_SUCCESS(rc) && Tag.uTag == ASN1_TAG_INTEGER) + { + /* RsaPublicKey.PublicExponent is at the end. */ + if (RTAsn1CursorIsEnd(&Cursor2)) + enmFormat = kKeyFormat_RsaPublicKey; + else + { + /* Check for INTEGER RsaPrivateKey.PublicExponent nad PrivateExponent before concluding. */ + rc = RTAsn1CursorReadHdr(RTAsn1CursorSkip(&Cursor2, Tag.cb), &Tag, "#5"); + if (RT_SUCCESS(rc) && Tag.uTag == ASN1_TAG_INTEGER) + { + rc = RTAsn1CursorReadHdr(RTAsn1CursorSkip(&Cursor2, Tag.cb), &Tag, "#6"); + if (RT_SUCCESS(rc) && Tag.uTag == ASN1_TAG_INTEGER) + enmFormat = kKeyFormat_RsaPrivateKey; + } + } + } + } + } + } + + if (enmFormat == kKeyFormat_Unknown) + return RTErrInfoSetF(pErrInfo, VERR_CR_KEY_UNKNOWN_TYPE, + "Unable to identify the key format (%.*Rhxs)", RT_MIN(16, pSection->cbData), pSection->pbData); + + /* + * Do the reading. + */ + int rc; + switch (enmFormat) + { + case kKeyFormat_RsaPublicKey: + rc = rtCrKeyCreateRsaPublic(phKey, pSection->pbData, (uint32_t)pSection->cbData, pErrInfo, pszErrorTag); + break; + + case kKeyFormat_RsaPrivateKey: + rc = rtCrKeyCreateRsaPrivate(phKey, pSection->pbData, (uint32_t)pSection->cbData, pErrInfo, pszErrorTag); + break; + + case kKeyFormat_RsaEncryptedPrivateKey: + { + uint8_t *pbDecrypted = NULL; + size_t cbDecrypted = 0; + size_t cbDecryptedAlloced = 0; + rc = rtCrKeyDecryptPemMessage(pszDekInfo, pszPassword, pSection->pbData, pSection->cbData, + &pbDecrypted, &cbDecrypted, &cbDecryptedAlloced, pErrInfo); + if (RT_SUCCESS(rc)) + { + int rc2 = rtCrKeyCreateRsaPrivate(phKey, pbDecrypted, (uint32_t)cbDecrypted, pErrInfo, pszErrorTag); + if (rc2 != VINF_SUCCESS) + rc = rc2; + RTMemSaferFree(pbDecrypted, cbDecryptedAlloced); + } + break; + } + + case kKeyFormat_SubjectPublicKeyInfo: + { + RTAsn1CursorInitPrimary(&PrimaryCursor, pSection->pbData, (uint32_t)pSection->cbData, + pErrInfo, &g_RTAsn1DefaultAllocator, RTASN1CURSOR_FLAGS_DER, pszErrorTag); + RTCRX509SUBJECTPUBLICKEYINFO SubjectPubKeyInfo; + RT_ZERO(SubjectPubKeyInfo); + rc = RTCrX509SubjectPublicKeyInfo_DecodeAsn1(&PrimaryCursor.Cursor, 0, &SubjectPubKeyInfo, "SubjectPubKeyInfo"); + if (RT_SUCCESS(rc)) + { + rc = RTCrKeyCreateFromSubjectPublicKeyInfo(phKey, &SubjectPubKeyInfo, pErrInfo, pszErrorTag); + RTCrX509SubjectPublicKeyInfo_Delete(&SubjectPubKeyInfo); + } + break; + } + + case kKeyFormat_PrivateKeyInfo: + rc = RTErrInfoSet(pErrInfo, VERR_CR_KEY_FORMAT_NOT_SUPPORTED, + "Support for PKCS#8 PrivateKeyInfo is not yet implemented"); + break; + + case kKeyFormat_EncryptedPrivateKeyInfo: + rc = RTErrInfoSet(pErrInfo, VERR_CR_KEY_FORMAT_NOT_SUPPORTED, + "Support for encrypted PKCS#8 PrivateKeyInfo is not yet implemented"); + break; + + default: + AssertFailedStmt(rc = VERR_INTERNAL_ERROR_4); + } + return rc; +} + + +RTDECL(int) RTCrKeyCreateFromBuffer(PRTCRKEY phKey, uint32_t fFlags, void const *pvSrc, size_t cbSrc, const char *pszPassword, + PRTERRINFO pErrInfo, const char *pszErrorTag) +{ + AssertReturn(!(fFlags & ~RTCRKEYFROM_F_VALID_MASK), VERR_INVALID_FLAGS); + PCRTCRPEMSECTION pSectionHead; + int rc = RTCrPemParseContent(pvSrc, cbSrc, fFlags, g_aRTCrKeyAllMarkers, g_cRTCrKeyAllMarkers, &pSectionHead, pErrInfo); + if (RT_SUCCESS(rc)) + { + if (pSectionHead) + { + rc = RTCrKeyCreateFromPemSection(phKey, pSectionHead, fFlags & ~RTCRKEYFROM_F_ONLY_PEM, pszPassword, + pErrInfo, pszErrorTag); + RTCrPemFreeSections(pSectionHead); + } + else + rc = rc != VINF_SUCCESS ? -rc : VERR_INTERNAL_ERROR_2; + } + return rc; +} + + +RTDECL(int) RTCrKeyCreateFromFile(PRTCRKEY phKey, uint32_t fFlags, const char *pszFilename, + const char *pszPassword, PRTERRINFO pErrInfo) +{ + AssertReturn(!(fFlags & ~RTCRKEYFROM_F_VALID_MASK), VERR_INVALID_FLAGS); + PCRTCRPEMSECTION pSectionHead; + int rc = RTCrPemReadFile(pszFilename, fFlags, g_aRTCrKeyAllMarkers, g_cRTCrKeyAllMarkers, &pSectionHead, pErrInfo); + if (RT_SUCCESS(rc)) + { + if (pSectionHead) + { + rc = RTCrKeyCreateFromPemSection(phKey, pSectionHead, fFlags & ~RTCRKEYFROM_F_ONLY_PEM, pszPassword, + pErrInfo, RTPathFilename(pszFilename)); + RTCrPemFreeSections(pSectionHead); + } + else + rc = rc != VINF_SUCCESS ? -rc : VERR_INTERNAL_ERROR_2; + } + return rc; +} + diff --git a/src/VBox/Runtime/common/crypto/key-internal.h b/src/VBox/Runtime/common/crypto/key-internal.h new file mode 100644 index 00000000..222bc16b --- /dev/null +++ b/src/VBox/Runtime/common/crypto/key-internal.h @@ -0,0 +1,122 @@ +/* $Id: key-internal.h $ */ +/** @file + * IPRT - Crypto - Cryptographic Keys, Internal Header. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef IPRT_INCLUDED_SRC_common_crypto_key_internal_h +#define IPRT_INCLUDED_SRC_common_crypto_key_internal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/crypto/key.h> +#include <iprt/bignum.h> + + +/** + * Cryptographic key - core bits. + */ +typedef struct RTCRKEYINT +{ + /** Magic value (RTCRKEYINT_MAGIC). */ + uint32_t u32Magic; + /** Reference counter. */ + uint32_t volatile cRefs; + /** The key type. */ + RTCRKEYTYPE enmType; + /** Flags, RTCRKEYINT_F_XXX. */ + uint32_t fFlags; + /** Number of bits in the key. */ + uint32_t cBits; + + /** Type specific data. */ + union + { + /** RTCRKEYTYPE_RSA_PRIVATE. */ + struct + { + /** The modulus. */ + RTBIGNUM Modulus; + /** The private exponent. */ + RTBIGNUM PrivateExponent; + /** The public exponent. */ + RTBIGNUM PublicExponent; + /** @todo add more bits as needed. */ + } RsaPrivate; + + /** RTCRKEYTYPE_RSA_PUBLIC. */ + struct + { + /** The modulus. */ + RTBIGNUM Modulus; + /** The exponent. */ + RTBIGNUM Exponent; + } RsaPublic; + } u; + +#if defined(IPRT_WITH_OPENSSL) + /** Size of raw key copy. */ + uint32_t cbEncoded; + /** Raw copy of the key, for openssl and such. + * If sensitive, this is a safer allocation, otherwise it follows the structure. */ + uint8_t *pbEncoded; +#endif +} RTCRKEYINT; +/** Pointer to a crypographic key. */ +typedef RTCRKEYINT *PRTCRKEYINT; +/** Pointer to a const crypographic key. */ +typedef RTCRKEYINT const *PCRTCRKEYINT; + + + +/** @name RTCRKEYINT_F_XXX. + * @{ */ +/** Key contains sensitive information, so no unnecessary copies. */ +#define RTCRKEYINT_F_SENSITIVE UINT32_C(0x00000001) +/** Set if private key bits are present. */ +#define RTCRKEYINT_F_PRIVATE UINT32_C(0x00000002) +/** Set if public key bits are present. */ +#define RTCRKEYINT_F_PUBLIC UINT32_C(0x00000004) +/** Set if the cbEncoded/pbEncoded members are present. */ +#define RTCRKEYINT_F_INCLUDE_ENCODED UINT32_C(0x00000008) +/** @} */ + +DECLHIDDEN(int) rtCrKeyCreateWorker(PRTCRKEYINT *ppThis, RTCRKEYTYPE enmType, uint32_t fFlags, + void const *pvEncoded, uint32_t cbEncoded); +DECLHIDDEN(int) rtCrKeyCreateRsaPublic(PRTCRKEY phKey, const void *pvKeyBits, uint32_t cbKeyBits, + PRTERRINFO pErrInfo, const char *pszErrorTag); +DECLHIDDEN(int) rtCrKeyCreateRsaPrivate(PRTCRKEY phKey, const void *pvKeyBits, uint32_t cbKeyBits, + PRTERRINFO pErrInfo, const char *pszErrorTag); + +#endif /* !IPRT_INCLUDED_SRC_common_crypto_key_internal_h */ diff --git a/src/VBox/Runtime/common/crypto/key-openssl.cpp b/src/VBox/Runtime/common/crypto/key-openssl.cpp new file mode 100644 index 00000000..d10df5af --- /dev/null +++ b/src/VBox/Runtime/common/crypto/key-openssl.cpp @@ -0,0 +1,229 @@ +/* $Id: key-openssl.cpp $ */ +/** @file + * IPRT - Crypto - Cryptographic Keys, OpenSSL glue. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/key.h> + +#include <iprt/err.h> +#include <iprt/string.h> +#include <iprt/crypto/digest.h> + + +#ifdef IPRT_WITH_OPENSSL +# include "internal/iprt-openssl.h" +# include "internal/magics.h" +# include "internal/openssl-pre.h" +# include <openssl/evp.h> +# include "internal/openssl-post.h" +# ifndef OPENSSL_VERSION_NUMBER +# error "Missing OPENSSL_VERSION_NUMBER!" +# endif + +# include "key-internal.h" + + +/** + * Creates an OpenSSL key for the given IPRT one, returning the message digest + * algorithm if desired. + * + * @returns IRPT status code. + * @param hKey The key to convert to an OpenSSL key. + * @param fNeedPublic Set if we need the public side of the key. + * @param pszAlgoObjId Alogrithm stuff we currently need. + * @param ppEvpKey Where to return the pointer to the key structure. + * @param ppEvpMdType Where to optionally return the message digest type. + * @param pErrInfo Where to optionally return more error details. + */ +DECLHIDDEN(int) rtCrKeyToOpenSslKey(RTCRKEY hKey, bool fNeedPublic, void /*EVP_PKEY*/ **ppEvpKey, PRTERRINFO pErrInfo) +{ + *ppEvpKey = NULL; + AssertReturn(hKey->u32Magic == RTCRKEYINT_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(fNeedPublic == !(hKey->fFlags & RTCRKEYINT_F_PRIVATE), VERR_WRONG_TYPE); + AssertReturn(hKey->fFlags & RTCRKEYINT_F_INCLUDE_ENCODED, VERR_WRONG_TYPE); /* build misconfig */ + + rtCrOpenSslInit(); + + /* + * Translate the key type from IPRT to EVP speak. + */ + int idKeyType; + switch (hKey->enmType) + { + case RTCRKEYTYPE_RSA_PRIVATE: + case RTCRKEYTYPE_RSA_PUBLIC: + idKeyType = EVP_PKEY_RSA; + break; + default: + return RTErrInfoSetF(pErrInfo, VERR_NOT_SUPPORTED, "Unsupported key type: %d", hKey->enmType); + } + + /* + * Allocate a new key structure and set its type. + */ + EVP_PKEY *pEvpNewKey = EVP_PKEY_new(); + if (!pEvpNewKey) + return RTErrInfoSetF(pErrInfo, VERR_NO_MEMORY, "EVP_PKEY_new/%d failed", idKeyType); + + /* + * Load the key into the structure. + */ + const unsigned char *puchPublicKey = hKey->pbEncoded; + EVP_PKEY *pRet; + if (fNeedPublic) + *ppEvpKey = pRet = d2i_PublicKey(idKeyType, &pEvpNewKey, &puchPublicKey, hKey->cbEncoded); + else + *ppEvpKey = pRet = d2i_PrivateKey(idKeyType, &pEvpNewKey, &puchPublicKey, hKey->cbEncoded); + if (pRet != NULL && pRet == pEvpNewKey) + return VINF_SUCCESS; + + /* Bail out: */ + EVP_PKEY_free(pEvpNewKey); + return RTErrInfoSet(pErrInfo, VERR_CR_PKIX_OSSL_D2I_PUBLIC_KEY_FAILED, + fNeedPublic ? "d2i_PublicKey failed" : "d2i_PrivateKey failed"); +} + + +/** + * Creates an OpenSSL key for the given IPRT one, returning the message digest + * algorithm if desired. + * + * @returns IRPT status code. + * @param hKey The key to convert to an OpenSSL key. + * @param fNeedPublic Set if we need the public side of the key. + * @param pszAlgoObjId Alogrithm stuff we currently need. + * @param ppEvpKey Where to return the pointer to the key structure. + * @param ppEvpMdType Where to optionally return the message digest type. + * @param pErrInfo Where to optionally return more error details. + */ +DECLHIDDEN(int) rtCrKeyToOpenSslKeyEx(RTCRKEY hKey, bool fNeedPublic, const char *pszAlgoObjId, + void /*EVP_PKEY*/ **ppEvpKey, const void /*EVP_MD*/ **ppEvpMdType, PRTERRINFO pErrInfo) +{ + *ppEvpKey = NULL; + if (ppEvpMdType) + *ppEvpMdType = NULL; + AssertReturn(hKey->u32Magic == RTCRKEYINT_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(fNeedPublic == !(hKey->fFlags & RTCRKEYINT_F_PRIVATE), VERR_WRONG_TYPE); + AssertReturn(hKey->fFlags & RTCRKEYINT_F_INCLUDE_ENCODED, VERR_WRONG_TYPE); /* build misconfig */ + + rtCrOpenSslInit(); + + /* + * Translate algorithm object ID into stuff that OpenSSL wants. + */ + int iAlgoNid = OBJ_txt2nid(pszAlgoObjId); + if (iAlgoNid == NID_undef) + return RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_OSSL_CIPHER_ALGO_NOT_KNOWN, + "Unknown public key algorithm [OpenSSL]: %s", pszAlgoObjId); + const char *pszAlgoSn = OBJ_nid2sn(iAlgoNid); + +# if OPENSSL_VERSION_NUMBER >= 0x10001000 && !defined(LIBRESSL_VERSION_NUMBER) + int idAlgoPkey = 0; + int idAlgoMd = 0; + if (!OBJ_find_sigid_algs(iAlgoNid, &idAlgoMd, &idAlgoPkey)) + return RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_OSSL_CIPHER_ALGO_NOT_KNOWN_EVP, + "OBJ_find_sigid_algs failed on %u (%s, %s)", iAlgoNid, pszAlgoSn, pszAlgoObjId); + if (ppEvpMdType) + { + const EVP_MD *pEvpMdType = EVP_get_digestbynid(idAlgoMd); + if (!pEvpMdType) + return RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_OSSL_CIPHER_ALGO_NOT_KNOWN_EVP, + "EVP_get_digestbynid failed on %d (%s, %s)", idAlgoMd, pszAlgoSn, pszAlgoObjId); + *ppEvpMdType = pEvpMdType; + } +# else + const EVP_MD *pEvpMdType = EVP_get_digestbyname(pszAlgoSn); + if (!pEvpMdType) + return RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_OSSL_CIPHER_ALGO_NOT_KNOWN_EVP, + "EVP_get_digestbyname failed on %s (%s)", pszAlgoSn, pszAlgoObjId); + if (ppEvpMdType) + *ppEvpMdType = pEvpMdType; +# endif + + /* + * Allocate a new key structure and set its type. + */ + EVP_PKEY *pEvpNewKey = EVP_PKEY_new(); + if (!pEvpNewKey) + return RTErrInfoSetF(pErrInfo, VERR_NO_MEMORY, "EVP_PKEY_new(%d) failed", iAlgoNid); + + int rc; +# if OPENSSL_VERSION_NUMBER >= 0x10001000 && !defined(LIBRESSL_VERSION_NUMBER) + if (EVP_PKEY_set_type(pEvpNewKey, idAlgoPkey)) + { + int idKeyType = EVP_PKEY_base_id(pEvpNewKey); +# else + int idKeyType = pEvpNewKey->type = EVP_PKEY_type(pEvpMdType->required_pkey_type[0]); +# endif + if (idKeyType != NID_undef) + + { + /* + * Load the key into the structure. + */ + const unsigned char *puchPublicKey = hKey->pbEncoded; + EVP_PKEY *pRet; + if (fNeedPublic) + *ppEvpKey = pRet = d2i_PublicKey(idKeyType, &pEvpNewKey, &puchPublicKey, hKey->cbEncoded); + else + *ppEvpKey = pRet = d2i_PrivateKey(idKeyType, &pEvpNewKey, &puchPublicKey, hKey->cbEncoded); + if (pRet != NULL && pRet == pEvpNewKey) + return VINF_SUCCESS; + + /* Bail out: */ + rc = RTErrInfoSet(pErrInfo, VERR_CR_PKIX_OSSL_D2I_PUBLIC_KEY_FAILED, + fNeedPublic ? "d2i_PublicKey failed" : "d2i_PrivateKey failed"); + } + else +# if OPENSSL_VERSION_NUMBER < 0x10001000 || defined(LIBRESSL_VERSION_NUMBER) + rc = RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_OSSL_EVP_PKEY_TYPE_ERROR, "EVP_PKEY_type() failed"); +# else + rc = RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_OSSL_EVP_PKEY_TYPE_ERROR, "EVP_PKEY_base_id() failed"); + } + else + rc = RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_OSSL_EVP_PKEY_TYPE_ERROR, + "EVP_PKEY_set_type(%u) failed (sig algo %s)", idAlgoPkey, pszAlgoSn); +# endif + + EVP_PKEY_free(pEvpNewKey); + return rc; +} + +#endif /* IPRT_WITH_OPENSSL */ + diff --git a/src/VBox/Runtime/common/crypto/key.cpp b/src/VBox/Runtime/common/crypto/key.cpp new file mode 100644 index 00000000..8cde24c9 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/key.cpp @@ -0,0 +1,395 @@ +/* $Id: key.cpp $ */ +/** @file + * IPRT - Crypto - Cryptographic Keys. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/key.h> + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/memsafer.h> +#include <iprt/string.h> +#include <iprt/crypto/rsa.h> +#include <iprt/crypto/pkix.h> + +#include "internal/magics.h" +#include "key-internal.h" + + +/** + * Internal crypto key instance creator. + * + * This does most of the common work, caller does the 'u' and cBits jobs. + * + * @returns IPRT status code. + * @param ppThis Where to return the key instance. + * @param enmType The key type. + * @param fFlags The key flags. + * @param pvEncoded The encoded key bits. + * @param cbEncoded The size of the encoded key bits (in bytes). + */ +DECLHIDDEN(int) rtCrKeyCreateWorker(PRTCRKEYINT *ppThis, RTCRKEYTYPE enmType, uint32_t fFlags, + void const *pvEncoded, uint32_t cbEncoded) +{ + PRTCRKEYINT pThis = (PRTCRKEYINT)RTMemAllocZ(sizeof(*pThis) + (fFlags & RTCRKEYINT_F_SENSITIVE ? 0 : cbEncoded)); + if (pThis) + { + pThis->enmType = enmType; + pThis->fFlags = fFlags; +#if defined(IPRT_WITH_OPENSSL) + pThis->fFlags |= RTCRKEYINT_F_INCLUDE_ENCODED; + pThis->cbEncoded = cbEncoded; + if (!(fFlags & RTCRKEYINT_F_SENSITIVE)) + pThis->pbEncoded = (uint8_t *)(pThis + 1); + else + { + pThis->pbEncoded = (uint8_t *)RTMemSaferAllocZ(cbEncoded); + if (!pThis->pbEncoded) + { + RTMemFree(pThis); + return VERR_NO_MEMORY; + } + } + memcpy(pThis->pbEncoded, pvEncoded, cbEncoded); +#else + RT_NOREF(pvEncoded, cbEncoded); +#endif + pThis->cRefs = 1; + pThis->u32Magic = RTCRKEYINT_MAGIC; + *ppThis = pThis; + return VINF_SUCCESS; + } + return VERR_NO_MEMORY; +} + + +/** + * Creates an RSA public key from a DER encoded RTCRRSAPUBLICKEY blob. + * + * @returns IPRT status code. + * @param phKey Where to return the key handle. + * @param pvKeyBits The DER encoded RTCRRSAPUBLICKEY blob. + * @param cbKeyBits The size of the blob. + * @param pErrInfo Where to supply addition error details. Optional. + * @param pszErrorTag Error tag. Optional. + */ +DECLHIDDEN(int) rtCrKeyCreateRsaPublic(PRTCRKEY phKey, const void *pvKeyBits, uint32_t cbKeyBits, + PRTERRINFO pErrInfo, const char *pszErrorTag) +{ + /* + * Decode the key data first since that's what's most likely to fail here. + */ + RTASN1CURSORPRIMARY PrimaryCursor; + RTAsn1CursorInitPrimary(&PrimaryCursor, pvKeyBits, cbKeyBits, pErrInfo, &g_RTAsn1DefaultAllocator, + RTASN1CURSOR_FLAGS_DER, pszErrorTag ? pszErrorTag : "rsa"); + RTCRRSAPUBLICKEY PublicKey; + RT_ZERO(PublicKey); + int rc = RTCrRsaPublicKey_DecodeAsn1(&PrimaryCursor.Cursor, 0, &PublicKey, pszErrorTag ? pszErrorTag : "PublicKey"); + if (RT_SUCCESS(rc)) + { + /* + * Create a key instance for it. + */ + PRTCRKEYINT pThis; + rc = rtCrKeyCreateWorker(&pThis, RTCRKEYTYPE_RSA_PUBLIC, RTCRKEYINT_F_PUBLIC, pvKeyBits, cbKeyBits); + if (RT_SUCCESS(rc)) + { + rc = RTAsn1Integer_ToBigNum(&PublicKey.Modulus, &pThis->u.RsaPublic.Modulus, 0); + if (RT_SUCCESS(rc)) + { + pThis->cBits = RTBigNumBitWidth(&pThis->u.RsaPublic.Modulus); + rc = RTAsn1Integer_ToBigNum(&PublicKey.PublicExponent, &pThis->u.RsaPublic.Exponent, 0); + if (RT_SUCCESS(rc)) + { + + /* Done. */ + RTAsn1VtDelete(&PublicKey.SeqCore.Asn1Core); + *phKey = pThis; + return VINF_SUCCESS; + } + } + RTCrKeyRelease(pThis); + } + RTAsn1VtDelete(&PublicKey.SeqCore.Asn1Core); + } + *phKey = NIL_RTCRKEY; + return rc; +} + + +RTDECL(int) RTCrKeyCreateFromPublicAlgorithmAndBits(PRTCRKEY phKey, PCRTASN1OBJID pAlgorithm, PCRTASN1BITSTRING pPublicKey, + PRTERRINFO pErrInfo, const char *pszErrorTag) +{ + /* + * Validate input. + */ + AssertPtrReturn(phKey, VERR_INVALID_POINTER); + *phKey = NIL_RTCRKEY; + + AssertPtrReturn(pAlgorithm, VERR_INVALID_POINTER); + AssertReturn(RTAsn1ObjId_IsPresent(pAlgorithm), VERR_INVALID_PARAMETER); + + AssertPtrReturn(pPublicKey, VERR_INVALID_POINTER); + AssertReturn(RTAsn1BitString_IsPresent(pPublicKey), VERR_INVALID_PARAMETER); + + /* + * Taking a weird shortcut here. + */ + PCRTCRPKIXSIGNATUREDESC pDesc = RTCrPkixSignatureFindByObjId(pAlgorithm, NULL); + if (pDesc && strcmp(pDesc->pszObjId, RTCRX509ALGORITHMIDENTIFIERID_RSA) == 0) + return rtCrKeyCreateRsaPublic(phKey, + RTASN1BITSTRING_GET_BIT0_PTR(pPublicKey), + RTASN1BITSTRING_GET_BYTE_SIZE(pPublicKey), + pErrInfo, pszErrorTag); + Assert(pDesc == NULL); + return RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_CIPHER_ALGO_NOT_KNOWN, "oid=%s", pAlgorithm->szObjId); +} + + +RTDECL(int) RTCrKeyCreateFromSubjectPublicKeyInfo(PRTCRKEY phKey, struct RTCRX509SUBJECTPUBLICKEYINFO const *pSrc, + PRTERRINFO pErrInfo, const char *pszErrorTag) +{ + AssertPtrReturn(pSrc, VERR_INVALID_POINTER); + AssertReturn(RTCrX509SubjectPublicKeyInfo_IsPresent(pSrc), VERR_INVALID_PARAMETER); + return RTCrKeyCreateFromPublicAlgorithmAndBits(phKey, &pSrc->Algorithm.Algorithm, &pSrc->SubjectPublicKey, + pErrInfo, pszErrorTag); +} + + +/** + * Creates an RSA private key from a DER encoded RTCRRSAPRIVATEKEY blob. + * + * @returns IPRT status code. + * @param phKey Where to return the key handle. + * @param pvKeyBits The DER encoded RTCRRSAPRIVATEKEY blob. + * @param cbKeyBits The size of the blob. + * @param pErrInfo Where to supply addition error details. Optional. + * @param pszErrorTag Error tag. Optional. + */ +DECLHIDDEN(int) rtCrKeyCreateRsaPrivate(PRTCRKEY phKey, const void *pvKeyBits, uint32_t cbKeyBits, + PRTERRINFO pErrInfo, const char *pszErrorTag) +{ + /* + * Decode the key data first since that's what's most likely to fail here. + */ + RTASN1CURSORPRIMARY PrimaryCursor; + RTAsn1CursorInitPrimary(&PrimaryCursor, pvKeyBits, cbKeyBits, pErrInfo, &g_RTAsn1SaferAllocator, + RTASN1CURSOR_FLAGS_DER, pszErrorTag ? pszErrorTag : "rsa"); + RTCRRSAPRIVATEKEY PrivateKey; + RT_ZERO(PrivateKey); + int rc = RTCrRsaPrivateKey_DecodeAsn1(&PrimaryCursor.Cursor, 0, &PrivateKey, pszErrorTag ? pszErrorTag : "PrivateKey"); + if (RT_SUCCESS(rc)) + { + /* + * Create a key instance for it. + */ + PRTCRKEYINT pThis; + rc = rtCrKeyCreateWorker(&pThis, RTCRKEYTYPE_RSA_PRIVATE, RTCRKEYINT_F_PRIVATE | RTCRKEYINT_F_SENSITIVE, + pvKeyBits, cbKeyBits); + if (RT_SUCCESS(rc)) + { + rc = RTAsn1Integer_ToBigNum(&PrivateKey.Modulus, &pThis->u.RsaPrivate.Modulus, 0); + if (RT_SUCCESS(rc)) + { + pThis->cBits = RTBigNumBitWidth(&pThis->u.RsaPrivate.Modulus); + rc = RTAsn1Integer_ToBigNum(&PrivateKey.PrivateExponent, &pThis->u.RsaPrivate.PrivateExponent, 0); + if (RT_SUCCESS(rc)) + { + rc = RTAsn1Integer_ToBigNum(&PrivateKey.PublicExponent, &pThis->u.RsaPrivate.PublicExponent, 0); + if (RT_SUCCESS(rc)) + { + /* Done. */ + RTAsn1VtDelete(&PrivateKey.SeqCore.Asn1Core); + RTMemWipeThoroughly(&PrivateKey, sizeof(PrivateKey), 3); + *phKey = pThis; + return VINF_SUCCESS; + } + } + } + RTCrKeyRelease(pThis); + } + RTAsn1VtDelete(&PrivateKey.SeqCore.Asn1Core); + RTMemWipeThoroughly(&PrivateKey, sizeof(PrivateKey), 3); + } + *phKey = NIL_RTCRKEY; + return rc; +} + + +RTDECL(uint32_t) RTCrKeyRetain(RTCRKEY hKey) +{ + PRTCRKEYINT pThis = hKey; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTCRKEYINT_MAGIC, UINT32_MAX); + + uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs); + AssertMsg(cRefs > 1 && cRefs < 1024, ("%#x\n", cRefs)); + return cRefs; +} + + +/** + * Destructor. + * + * @returns 0 + * @param pThis The key to destroy. + */ +static int rtCrKeyDestroy(PRTCRKEYINT pThis) +{ + /* Invalidate the object. */ + pThis->u32Magic = ~RTCRKEYINT_MAGIC; + + /* Type specific cleanup. */ + switch (pThis->enmType) + { + case RTCRKEYTYPE_RSA_PUBLIC: + RTBigNumDestroy(&pThis->u.RsaPublic.Modulus); + RTBigNumDestroy(&pThis->u.RsaPublic.Exponent); + break; + + case RTCRKEYTYPE_RSA_PRIVATE: + RTBigNumDestroy(&pThis->u.RsaPrivate.Modulus); + RTBigNumDestroy(&pThis->u.RsaPrivate.PrivateExponent); + RTBigNumDestroy(&pThis->u.RsaPrivate.PublicExponent); + break; + + case RTCRKEYTYPE_INVALID: + case RTCRKEYTYPE_END: + case RTCRKEYTYPE_32BIT_HACK: + AssertFailed(); + } + pThis->enmType = RTCRKEYTYPE_INVALID; + +#if defined(IPRT_WITH_OPENSSL) + /* Free the encoded form if sensitive (otherwise it follows pThis). */ + if (pThis->pbEncoded) + { + if (pThis->fFlags & RTCRKEYINT_F_SENSITIVE) + RTMemSaferFree((uint8_t *)pThis->pbEncoded, pThis->cbEncoded); + else + Assert(pThis->pbEncoded == (uint8_t *)(pThis + 1)); + pThis->pbEncoded = NULL; + } +#endif + + /* Finally, free the key object itself. */ + RTMemFree(pThis); + return 0; +} + + +RTDECL(uint32_t) RTCrKeyRelease(RTCRKEY hKey) +{ + if (hKey == NIL_RTCRKEY) + return 0; + PRTCRKEYINT pThis = hKey; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTCRKEYINT_MAGIC, UINT32_MAX); + + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + AssertMsg(cRefs < 1024, ("%#x\n", cRefs)); + if (cRefs != 0) + return cRefs; + return rtCrKeyDestroy(pThis); +} + + +RTDECL(RTCRKEYTYPE) RTCrKeyGetType(RTCRKEY hKey) +{ + PRTCRKEYINT pThis = hKey; + AssertPtrReturn(pThis, RTCRKEYTYPE_INVALID); + AssertReturn(pThis->u32Magic == RTCRKEYINT_MAGIC, RTCRKEYTYPE_INVALID); + return pThis->enmType; +} + + +RTDECL(bool) RTCrKeyHasPrivatePart(RTCRKEY hKey) +{ + PRTCRKEYINT pThis = hKey; + AssertPtrReturn(pThis, false); + AssertReturn(pThis->u32Magic == RTCRKEYINT_MAGIC, false); + return RT_BOOL(pThis->fFlags & RTCRKEYINT_F_PRIVATE); +} + + +RTDECL(bool) RTCrKeyHasPublicPart(RTCRKEY hKey) +{ + PRTCRKEYINT pThis = hKey; + AssertPtrReturn(pThis, false); + AssertReturn(pThis->u32Magic == RTCRKEYINT_MAGIC, false); + return RT_BOOL(pThis->fFlags & RTCRKEYINT_F_PUBLIC); +} + + +RTDECL(uint32_t) RTCrKeyGetBitCount(RTCRKEY hKey) +{ + PRTCRKEYINT pThis = hKey; + AssertPtrReturn(pThis, 0); + AssertReturn(pThis->u32Magic == RTCRKEYINT_MAGIC, 0); + return pThis->cBits; +} + + +RTDECL(int) RTCrKeyQueryRsaModulus(RTCRKEY hKey, PRTBIGNUM pModulus) +{ + PRTCRKEYINT pThis = hKey; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRKEYINT_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(pThis->enmType == RTCRKEYTYPE_RSA_PRIVATE || pThis->enmType == RTCRKEYTYPE_RSA_PUBLIC, VERR_WRONG_TYPE); + AssertPtrReturn(pModulus, VERR_INVALID_POINTER); + + if (pThis->enmType == RTCRKEYTYPE_RSA_PRIVATE) + return RTBigNumAssign(pModulus, &pThis->u.RsaPrivate.Modulus); + return RTBigNumAssign(pModulus, &pThis->u.RsaPublic.Modulus); +} + + +RTDECL(int) RTCrKeyQueryRsaPrivateExponent(RTCRKEY hKey, PRTBIGNUM pPrivateExponent) +{ + PRTCRKEYINT pThis = hKey; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRKEYINT_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(pThis->enmType == RTCRKEYTYPE_RSA_PRIVATE, VERR_WRONG_TYPE); + AssertPtrReturn(pPrivateExponent, VERR_INVALID_POINTER); + + return RTBigNumAssign(pPrivateExponent, &pThis->u.RsaPrivate.PrivateExponent); +} + diff --git a/src/VBox/Runtime/common/crypto/pemfile-read.cpp b/src/VBox/Runtime/common/crypto/pemfile-read.cpp new file mode 100644 index 00000000..70408688 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/pemfile-read.cpp @@ -0,0 +1,663 @@ +/* $Id: pemfile-read.cpp $ */ +/** @file + * IPRT - Crypto - PEM file reader. + * + * See RFC-1341 for the original ideas for the format, but keep in mind + * that the format was hijacked and put to different uses. We're aiming at + * dealing with the different uses rather than anything email related here. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/pem.h> + +#include <iprt/asm.h> +#include <iprt/base64.h> +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/memsafer.h> +#include <iprt/file.h> +#include <iprt/string.h> + + + +/** + * Looks for a PEM-like marker. + * + * @returns true if found, false if not. + * @param pbContent Start of the content to search thru. + * @param cbContent The size of the content to search. + * @param offStart The offset into pbContent to start searching. + * @param pszLeadWord The lead word (BEGIN/END). + * @param cchLeadWord The length of the lead word. + * @param paMarkers Pointer to an array of markers. + * @param cMarkers Number of markers in the array. + * @param ppMatch Where to return the pointer to the matching + * marker. Optional. + * @param poffBegin Where to return the start offset of the marker. + * Optional. + * @param poffEnd Where to return the end offset of the marker + * (trailing whitespace and newlines will be + * skipped). Optional. + */ +static bool rtCrPemFindMarker(uint8_t const *pbContent, size_t cbContent, size_t offStart, + const char *pszLeadWord, size_t cchLeadWord, PCRTCRPEMMARKER paMarkers, size_t cMarkers, + PCRTCRPEMMARKER *ppMatch, size_t *poffBegin, size_t *poffEnd) +{ + /* Remember the start of the content for the purpose of calculating offsets. */ + uint8_t const * const pbStart = pbContent; + + /* Skip adhead by offStart */ + if (offStart >= cbContent) + return false; + pbContent += offStart; + cbContent -= offStart; + + /* + * Search the content. + */ + while (cbContent > 6) + { + /* + * Look for dashes. + */ + uint8_t const *pbStartSearch = pbContent; + pbContent = (uint8_t const *)memchr(pbContent, '-', cbContent); + if (!pbContent) + break; + + cbContent -= pbContent - pbStartSearch; + if (cbContent < 6) + break; + + /* + * There must be at least three to interest us. + */ + if ( pbContent[1] == '-' + && pbContent[2] == '-') + { + unsigned cDashes = 3; + while (cDashes < cbContent && pbContent[cDashes] == '-') + cDashes++; + + if (poffBegin) + *poffBegin = pbContent - pbStart; + cbContent -= cDashes; + pbContent += cDashes; + + /* + * Match lead word. + */ + if ( cbContent > cchLeadWord + && memcmp(pbContent, pszLeadWord, cchLeadWord) == 0 + && RT_C_IS_BLANK(pbContent[cchLeadWord]) ) + { + pbContent += cchLeadWord; + cbContent -= cchLeadWord; + while (cbContent > 0 && RT_C_IS_BLANK(*pbContent)) + { + pbContent++; + cbContent--; + } + + /* + * Match one of the specified markers. + */ + uint8_t const *pbSavedContent = pbContent; + size_t const cbSavedContent = cbContent; + for (uint32_t iMarker = 0; iMarker < cMarkers; iMarker++) + { + pbContent = pbSavedContent; + cbContent = cbSavedContent; + + uint32_t cWords = paMarkers[iMarker].cWords; + PCRTCRPEMMARKERWORD pWord = paMarkers[iMarker].paWords; + while (cWords > 0) + { + uint32_t const cchWord = pWord->cchWord; + if (cbContent <= cchWord) + break; + if (memcmp(pbContent, pWord->pszWord, cchWord)) + break; + pbContent += cchWord; + cbContent -= cchWord; + + if (!cbContent) + break; + if (RT_C_IS_BLANK(*pbContent)) + do + { + pbContent++; + cbContent--; + } while (cbContent > 0 && RT_C_IS_BLANK(*pbContent)); + else if (cWords > 1 || pbContent[0] != '-') + break; + + cWords--; + if (cWords == 0) + { + /* + * If there are three or more dashes following now, we've got a hit. + */ + if ( cbContent > 3 + && pbContent[0] == '-' + && pbContent[1] == '-' + && pbContent[2] == '-') + { + cDashes = 3; + while (cDashes < cbContent && pbContent[cDashes] == '-') + cDashes++; + cbContent -= cDashes; + pbContent += cDashes; + + /* + * Skip spaces and newline. + */ + while (cbContent > 0 && RT_C_IS_SPACE(*pbContent)) + pbContent++, cbContent--; + if (poffEnd) + *poffEnd = pbContent - pbStart; + if (ppMatch) + *ppMatch = &paMarkers[iMarker]; + return true; + } + break; + } + pWord++; + } /* for each word in marker. */ + } /* for each marker. */ + } + } + else + { + pbContent++; + cbContent--; + } + } + + return false; +} + + +static bool rtCrPemFindMarkerSection(uint8_t const *pbContent, size_t cbContent, size_t offStart, + PCRTCRPEMMARKER paMarkers, size_t cMarkers, + PCRTCRPEMMARKER *ppMatch, size_t *poffBegin, size_t *poffEnd, size_t *poffResume) +{ + /** @todo Detect BEGIN / END mismatch. */ + PCRTCRPEMMARKER pMatch; + if (rtCrPemFindMarker(pbContent, cbContent, offStart, "BEGIN", 5, paMarkers, cMarkers, + &pMatch, NULL /*poffStart*/, poffBegin)) + { + if (rtCrPemFindMarker(pbContent, cbContent, *poffBegin, "END", 3, pMatch, 1, + NULL /*ppMatch*/, poffEnd, poffResume)) + { + *ppMatch = pMatch; + return true; + } + } + *ppMatch = NULL; + return false; +} + + +/** + * Parses any fields the message may contain. + * + * @retval VINF_SUCCESS + * @retval VERR_NO_MEMORY + * @retval VERR_CR_MALFORMED_PEM_HEADER + * + * @param pSection The current section, where we will attach a list of + * fields to the pFieldHead member. + * @param pbContent The content of the PEM message being parsed. + * @param cbContent The length of the PEM message. + * @param pcbFields Where to return the length of the header fields we found. + */ +static int rtCrPemProcessFields(PRTCRPEMSECTION pSection, uint8_t const *pbContent, size_t cbContent, size_t *pcbFields) +{ + uint8_t const * const pbContentStart = pbContent; + + /* + * Work the encapulated header protion field by field. + * + * This is optional, so currently we don't throw errors here but leave that + * to when we work the text portion with the base64 decoder. Also, as a reader + * we don't go all pedanic on confirming to specification (RFC-1421), especially + * given that it's used for crypto certificates, keys and the like not email. :-) + */ + PCRTCRPEMFIELD *ppNext = &pSection->pFieldHead; + while (cbContent > 0) + { + /* Just look for a colon first. */ + const uint8_t *pbColon = (const uint8_t *)memchr(pbContent, ':', cbContent); + if (!pbColon) + break; + size_t offColon = pbColon - pbContent; + + /* Check that the colon is within the first line. */ + if (!memchr(pbContent, '\n', cbContent - offColon)) + return VERR_CR_MALFORMED_PEM_HEADER; + + /* Skip leading spaces (there shouldn't be any, but just in case). */ + while (RT_C_IS_BLANK(*pbContent) && /*paranoia:*/ offColon > 0) + { + offColon--; + cbContent--; + pbContent++; + } + + /* There shouldn't be any spaces before the colon, but just in case */ + size_t cchName = offColon; + while (cchName > 0 && RT_C_IS_BLANK(pbContent[cchName - 1])) + cchName--; + + /* Skip leading value spaces (there typically is at least one). */ + size_t offValue = offColon + 1; + while (offValue < cbContent && RT_C_IS_BLANK(pbContent[offValue])) + offValue++; + + /* Find the newline the field value ends with and where the next iteration should start later on. */ + size_t cbLeft; + uint8_t const *pbNext = (uint8_t const *)memchr(&pbContent[offValue], '\n', cbContent - offValue); + while ( pbNext + && (cbLeft = pbNext - pbContent) < cbContent + && RT_C_IS_BLANK(pbNext[1]) /* next line must start with a space or tab */) + pbNext = (uint8_t const *)memchr(&pbNext[1], '\n', cbLeft - 1); + + size_t cchValue; + if (pbNext) + { + cchValue = pbNext - &pbContent[offValue]; + if (cchValue > 0 && pbNext[-1] == '\r') + cchValue--; + pbNext++; + } + else + { + cchValue = cbContent - offValue; + pbNext = &pbContent[cbContent]; + } + + /* Strip trailing spaces. */ + while (cchValue > 0 && RT_C_IS_BLANK(pbContent[offValue + cchValue - 1])) + cchValue--; + + /* + * Allocate a field instance. + * + * Note! We don't consider field data sensitive at the moment. This + * mainly because the fields are chiefly used to indicate the + * encryption parameters to the body. + */ + PRTCRPEMFIELD pNewField = (PRTCRPEMFIELD)RTMemAllocZVar(sizeof(*pNewField) + cchName + 1 + cchValue + 1); + if (!pNewField) + return VERR_NO_MEMORY; + pNewField->cchName = cchName; + pNewField->cchValue = cchValue; + memcpy(pNewField->szName, pbContent, cchName); + pNewField->szName[cchName] = '\0'; + char *pszDst = (char *)memcpy(&pNewField->szName[cchName + 1], &pbContent[offValue], cchValue); + pNewField->pszValue = pszDst; + pszDst[cchValue] = '\0'; + pNewField->pNext = NULL; + + *ppNext = pNewField; + ppNext = &pNewField->pNext; + + /* + * Advance past the field. + */ + cbContent -= pbNext - pbContent; + pbContent = pbNext; + } + + /* + * Skip blank line(s) before the body. + */ + while (cbContent >= 1) + { + size_t cbSkip; + if (pbContent[0] == '\n') + cbSkip = 1; + else if ( pbContent[0] == '\r' + && cbContent >= 2 + && pbContent[1] == '\n') + cbSkip = 2; + else + break; + pbContent += cbSkip; + cbContent -= cbSkip; + } + + *pcbFields = pbContent - pbContentStart; + return VINF_SUCCESS; +} + + +/** + * Does the decoding of a PEM-like data blob after it has been located. + * + * @returns IPRT status ocde + * @param pbContent The start of the PEM-like content (text). + * @param cbContent The max size of the PEM-like content. + * @param fSensitive Set if the safer allocator should be used. + * @param ppvDecoded Where to return a heap block containing the + * decoded content. + * @param pcbDecoded Where to return the size of the decoded content. + */ +static int rtCrPemDecodeBase64(uint8_t const *pbContent, size_t cbContent, bool fSensitive, + void **ppvDecoded, size_t *pcbDecoded) +{ + ssize_t cbDecoded = RTBase64DecodedSizeEx((const char *)pbContent, cbContent, NULL); + if (cbDecoded < 0) + return VERR_INVALID_BASE64_ENCODING; + + *pcbDecoded = cbDecoded; + void *pvDecoded = !fSensitive ? RTMemAlloc(cbDecoded) : RTMemSaferAllocZ(cbDecoded); + if (!pvDecoded) + return VERR_NO_MEMORY; + + size_t cbActual; + int rc = RTBase64DecodeEx((const char *)pbContent, cbContent, pvDecoded, cbDecoded, &cbActual, NULL); + if (RT_SUCCESS(rc)) + { + if (cbActual == (size_t)cbDecoded) + { + *ppvDecoded = pvDecoded; + return VINF_SUCCESS; + } + + rc = VERR_INTERNAL_ERROR_3; + } + if (!fSensitive) + RTMemFree(pvDecoded); + else + RTMemSaferFree(pvDecoded, cbDecoded); + return rc; +} + + +/** + * Checks if the content of a file looks to be binary or not. + * + * @returns true if likely to be binary, false if not binary. + * @param pbFile The file bytes to scan. + * @param cbFile The number of bytes. + * @param fFlags RTCRPEMREADFILE_F_XXX + */ +static bool rtCrPemIsBinaryBlob(uint8_t const *pbFile, size_t cbFile, uint32_t fFlags) +{ + if (fFlags & RTCRPEMREADFILE_F_ONLY_PEM) + return false; + + /* + * Well formed PEM files should probably only contain 7-bit ASCII and + * restrict thenselfs to the following control characters: + * tab, newline, return, form feed + * + * However, if we want to read PEM files which contains human readable + * certificate details before or after each base-64 section, we can't stick + * to 7-bit ASCII. We could say it must be UTF-8, but that's probably to + * limited as well. So, we'll settle for detecting binary files by control + * characters alone (safe enough for DER encoded stuff, I think). + */ + while (cbFile-- > 0) + { + uint8_t const b = *pbFile++; + if (b < 32 && b != '\t' && b != '\n' && b != '\r' && b != '\f') + { + /* Ignore EOT (4), SUB (26) and NUL (0) at the end of the file. */ + if ( (b == 4 || b == 26) + && ( cbFile == 0 + || ( cbFile == 1 + && *pbFile == '\0'))) + return false; + + if (b == 0 && cbFile == 0) + return false; + + return true; + } + } + return false; +} + + +RTDECL(int) RTCrPemFreeSections(PCRTCRPEMSECTION pSectionHead) +{ + while (pSectionHead != NULL) + { + PRTCRPEMSECTION pFree = (PRTCRPEMSECTION)pSectionHead; + pSectionHead = pSectionHead->pNext; + ASMCompilerBarrier(); /* paranoia */ + + if (pFree->pbData) + { + if (!pFree->fSensitive) + RTMemFree(pFree->pbData); + else + RTMemSaferFree(pFree->pbData, pFree->cbData); + pFree->pbData = NULL; + pFree->cbData = 0; + } + + PRTCRPEMFIELD pField = (PRTCRPEMFIELD)pFree->pFieldHead; + if (pField) + { + pFree->pFieldHead = NULL; + do + { + PRTCRPEMFIELD pFreeField = pField; + pField = (PRTCRPEMFIELD)pField->pNext; + ASMCompilerBarrier(); /* paranoia */ + + pFreeField->pszValue = NULL; + RTMemFree(pFreeField); + } while (pField); + } + + RTMemFree(pFree); + } + return VINF_SUCCESS; +} + + +RTDECL(int) RTCrPemParseContent(void const *pvContent, size_t cbContent, uint32_t fFlags, + PCRTCRPEMMARKER paMarkers, size_t cMarkers, + PCRTCRPEMSECTION *ppSectionHead, PRTERRINFO pErrInfo) +{ + RT_NOREF_PV(pErrInfo); + + /* + * Input validation. + */ + AssertPtr(ppSectionHead); + *ppSectionHead = NULL; + AssertReturn(cbContent, VINF_EOF); + AssertPtr(pvContent); + AssertPtr(paMarkers); + AssertReturn(!(fFlags & ~RTCRPEMREADFILE_F_VALID_MASK), VERR_INVALID_FLAGS); + + /* + * Pre-allocate a section. + */ + int rc = VINF_SUCCESS; + PRTCRPEMSECTION pSection = (PRTCRPEMSECTION)RTMemAllocZ(sizeof(*pSection)); + if (pSection) + { + bool const fSensitive = RT_BOOL(fFlags & RTCRPEMREADFILE_F_SENSITIVE); + + /* + * Try locate the first section. + */ + uint8_t const *pbContent = (uint8_t const *)pvContent; + size_t offBegin, offEnd, offResume; + PCRTCRPEMMARKER pMatch; + if ( !rtCrPemIsBinaryBlob(pbContent, cbContent, fFlags) + && rtCrPemFindMarkerSection(pbContent, cbContent, 0 /*offStart*/, paMarkers, cMarkers, + &pMatch, &offBegin, &offEnd, &offResume) ) + { + PCRTCRPEMSECTION *ppNext = ppSectionHead; + for (;;) + { + //pSection->pNext = NULL; + pSection->pMarker = pMatch; + //pSection->pbData = NULL; + //pSection->cbData = 0; + //pSection->pFieldHead = NULL; + pSection->fSensitive = fSensitive; + + *ppNext = pSection; + ppNext = &pSection->pNext; + + /* + * Decode the section. + */ + size_t cbFields = 0; + int rc2 = rtCrPemProcessFields(pSection, pbContent + offBegin, offEnd - offBegin, &cbFields); + offBegin += cbFields; + if (RT_SUCCESS(rc2)) + rc2 = rtCrPemDecodeBase64(pbContent + offBegin, offEnd - offBegin, fSensitive, + (void **)&pSection->pbData, &pSection->cbData); + if (RT_FAILURE(rc2)) + { + pSection->pbData = NULL; + pSection->cbData = 0; + if ( rc2 == VERR_INVALID_BASE64_ENCODING + && (fFlags & RTCRPEMREADFILE_F_CONTINUE_ON_ENCODING_ERROR)) + rc = -rc2; + else + { + rc = rc2; + break; + } + } + + /* + * More sections? + */ + if ( offResume + 12 >= cbContent + || offResume >= cbContent + || !rtCrPemFindMarkerSection(pbContent, cbContent, offResume, paMarkers, cMarkers, + &pMatch, &offBegin, &offEnd, &offResume) ) + break; /* No. */ + + /* Ok, allocate a new record for it. */ + pSection = (PRTCRPEMSECTION)RTMemAllocZ(sizeof(*pSection)); + if (RT_UNLIKELY(!pSection)) + { + rc = VERR_NO_MEMORY; + break; + } + } + if (RT_SUCCESS(rc)) + return rc; + + RTCrPemFreeSections(*ppSectionHead); + } + else + { + if (!(fFlags & RTCRPEMREADFILE_F_ONLY_PEM)) + { + /* + * No PEM section found. Return the whole file as one binary section. + */ + //pSection->pNext = NULL; + //pSection->pMarker = NULL; + //pSection->pFieldHead = NULL; + pSection->cbData = cbContent; + pSection->fSensitive = fSensitive; + if (!fSensitive) + pSection->pbData = (uint8_t *)RTMemDup(pbContent, cbContent); + else + { + pSection->pbData = (uint8_t *)RTMemSaferAllocZ(cbContent); + if (pSection->pbData) + memcpy(pSection->pbData, pbContent, cbContent); + } + if (pSection->pbData) + { + *ppSectionHead = pSection; + return VINF_SUCCESS; + } + + rc = VERR_NO_MEMORY; + } + else + rc = VWRN_NOT_FOUND; + RTMemFree(pSection); + } + } + else + rc = VERR_NO_MEMORY; + *ppSectionHead = NULL; + return rc; +} + + +RTDECL(int) RTCrPemReadFile(const char *pszFilename, uint32_t fFlags, PCRTCRPEMMARKER paMarkers, size_t cMarkers, + PCRTCRPEMSECTION *ppSectionHead, PRTERRINFO pErrInfo) +{ + *ppSectionHead = NULL; + AssertReturn(!(fFlags & ~RTCRPEMREADFILE_F_VALID_MASK), VERR_INVALID_FLAGS); + + size_t cbContent; + void *pvContent; + int rc = RTFileReadAllEx(pszFilename, 0, 64U*_1M, RTFILE_RDALL_O_DENY_WRITE, &pvContent, &cbContent); + if (RT_SUCCESS(rc)) + { + rc = RTCrPemParseContent(pvContent, cbContent, fFlags, paMarkers, cMarkers, ppSectionHead, pErrInfo); + if (fFlags & RTCRPEMREADFILE_F_SENSITIVE) + RTMemWipeThoroughly(pvContent, cbContent, 3); + RTFileReadAllFree(pvContent, cbContent); + } + else + rc = RTErrInfoSetF(pErrInfo, rc, "RTFileReadAllEx failed with %Rrc on '%s'", rc, pszFilename); + return rc; +} + + +RTDECL(const char *) RTCrPemFindFirstSectionInContent(void const *pvContent, size_t cbContent, + PCRTCRPEMMARKER paMarkers, size_t cMarkers) +{ + size_t offBegin; + if (rtCrPemFindMarker((uint8_t *)pvContent, cbContent, 0, "BEGIN", 5, paMarkers, cMarkers, NULL, &offBegin, NULL)) + return (const char *)pvContent + offBegin; + return NULL; +} + diff --git a/src/VBox/Runtime/common/crypto/pemfile-write.cpp b/src/VBox/Runtime/common/crypto/pemfile-write.cpp new file mode 100644 index 00000000..d0425385 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/pemfile-write.cpp @@ -0,0 +1,266 @@ +/* $Id: pemfile-write.cpp $ */ +/** @file + * IPRT - Crypto - PEM file writer. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/pem.h> + +#include <iprt/asn1.h> +#include <iprt/base64.h> +#include <iprt/errcore.h> +#include <iprt/string.h> +#include <iprt/vfs.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Used by rtCrPemWriteAsn1Callback to buffer data before outputting it as + * BASE64. + * + * An encoded line is 64 characters long plus a newline, covering 48 bytes + * of binary data. We want about 4KB of output: + * 4096 / 65 = 63.015384615384615384615384615385 + * 64 * 65 + 1 = 4161 (0x1041) + */ +typedef struct PEMOUTPUTASN1 +{ + size_t cbPending; + PFNRTSTROUTPUT pfnOutput; + void *pvUser; + size_t cchRet; + uint8_t abBlock[0x0c00]; + char szBlock[0x1060]; +} PEMOUTPUTASN1; +typedef PEMOUTPUTASN1 *PPEMOUTPUTASN1; + + + +RTDECL(size_t) RTCrPemWriteBlob(PFNRTSTROUTPUT pfnOutput, void *pvUser, + const void *pvContent, size_t cbContent, const char *pszMarker) +{ + /* + * -----BEGIN XXXXX----- + */ + size_t cchRet = pfnOutput(pvUser, RT_STR_TUPLE("-----BEGIN ")); + size_t const cchMarker = strlen(pszMarker); + cchRet += pfnOutput(pvUser, pszMarker, cchMarker); + cchRet += pfnOutput(pvUser, RT_STR_TUPLE("-----\n")); + + /* + * base64 - in reasonably sized stack blocks. + * An encoded line is 64 characters long plus a newline, covering 48 bytes + * of binary data. We want about 4KB of output: + * 4096 / 65 = 63.015384615384615384615384615385 + * 64 * 65 + 1 = 4161 (0x1041) + */ + const size_t cbMaxBlock = 64 * 48; + while (cbContent > 0) + { + char szBlock[0x1060]; + size_t cbBlock = RT_MIN(cbContent, cbMaxBlock); + size_t cchBlock = 0; + int rc = RTBase64EncodeEx(pvContent, cbBlock, RTBASE64_FLAGS_EOL_LF, + szBlock, sizeof(szBlock), &cchBlock); + AssertRC(rc); + szBlock[cchBlock++] = '\n'; + szBlock[cchBlock] = '\0'; + + cchRet += pfnOutput(pvUser, szBlock, cchBlock); + + pvContent = (uint8_t const *)pvContent + cbBlock; + cbContent -= cbBlock; + } + + /* + * -----END XXXXX----- + */ + cchRet += pfnOutput(pvUser, RT_STR_TUPLE("-----END ")); + cchRet += pfnOutput(pvUser, pszMarker, cchMarker); + cchRet += pfnOutput(pvUser, RT_STR_TUPLE("-----\n")); + + /* termination call */ + cchRet += pfnOutput(pvUser, NULL, 0); + + return cchRet; +} + + +RTDECL(ssize_t) RTCrPemWriteBlobToVfsIoStrm(RTVFSIOSTREAM hVfsIos, const void *pvContent, size_t cbContent, const char *pszMarker) +{ + VFSIOSTRMOUTBUF Buf; + VFSIOSTRMOUTBUF_INIT(&Buf, hVfsIos); + size_t cchRet = RTCrPemWriteBlob(RTVfsIoStrmStrOutputCallback, &Buf, pvContent, cbContent, pszMarker); + Assert(Buf.offBuf == 0); + return RT_SUCCESS(Buf.rc) ? (ssize_t)cchRet : Buf.rc; +} + + +RTDECL(ssize_t) RTCrPemWriteBlobToVfsFile(RTVFSFILE hVfsFile, const void *pvContent, size_t cbContent, const char *pszMarker) +{ + RTVFSIOSTREAM hVfsIos = RTVfsFileToIoStream(hVfsFile); + AssertReturn(hVfsIos != NIL_RTVFSIOSTREAM, VERR_INVALID_HANDLE); + ssize_t cchRet = RTCrPemWriteBlobToVfsIoStrm(hVfsIos, pvContent, cbContent, pszMarker); + RTVfsIoStrmRelease(hVfsIos); + return cchRet; +} + + +/** @callback_method_impl{FNRTASN1ENCODEWRITER} */ +static DECLCALLBACK(int) rtCrPemWriteAsn1Callback(const void *pvBuf, size_t cbToWrite, void *pvUser, PRTERRINFO pErrInfo) +{ + PPEMOUTPUTASN1 pThis = (PPEMOUTPUTASN1)pvUser; + AssertCompile((sizeof(pThis->abBlock) % 48) == 0); + + while (cbToWrite > 0) + { + size_t offDst = pThis->cbPending; + AssertStmt(offDst <= sizeof(pThis->abBlock), offDst = sizeof(pThis->abBlock)); + size_t cbDst = sizeof(pThis->abBlock) - offDst; + if (cbToWrite < cbDst) + { + /* Buffer not full: Append and return. */ + memcpy(&pThis->abBlock[offDst], pvBuf, cbToWrite); + pThis->cbPending = offDst + cbToWrite; + break; + } + + /* Fill the buffer and flush it: */ + memcpy(&pThis->abBlock[offDst], pvBuf, cbDst); + Assert(offDst + cbDst == sizeof(pThis->abBlock)); + + size_t cchBlock = 0; + int rc = RTBase64EncodeEx(pThis->abBlock, sizeof(pThis->abBlock), RTBASE64_FLAGS_EOL_LF, + pThis->szBlock, sizeof(pThis->szBlock), &cchBlock); + AssertRC(rc); + pThis->szBlock[cchBlock++] = '\n'; + pThis->szBlock[cchBlock] = '\0'; + + pThis->cchRet += pThis->pfnOutput(pThis->pvUser, pThis->szBlock, cchBlock); + pThis->cbPending = 0; + + /* Advance. */ + pvBuf = (uint8_t const *)pvBuf + cbDst; + cbToWrite -= cbDst; + } + + RT_NOREF(pErrInfo); + return VINF_SUCCESS; +} + + +RTDECL(ssize_t) RTCrPemWriteAsn1(PFNRTSTROUTPUT pfnOutput, void *pvUser, PRTASN1CORE pRoot, + uint32_t fFlags, const char *pszMarker, PRTERRINFO pErrInfo) +{ + AssertReturn(!fFlags, VERR_INVALID_FLAGS); + + /* + * Prepare the ASN.1 data for DER encoding. + */ + int rc = RTAsn1EncodePrepare(pRoot, RTASN1ENCODE_F_DER, NULL /*pcbEncoded*/, pErrInfo); + AssertRCReturn(rc, rc); + + /* + * -----BEGIN XXXXX----- + */ + size_t cchRet = pfnOutput(pvUser, RT_STR_TUPLE("-----BEGIN ")); + size_t const cchMarker = strlen(pszMarker); + cchRet += pfnOutput(pvUser, pszMarker, cchMarker); + cchRet += pfnOutput(pvUser, RT_STR_TUPLE("-----\n")); + + /* + * BASE64 + */ + PEMOUTPUTASN1 This; + This.pfnOutput = pfnOutput; + This.pvUser = pvUser; + This.cchRet = 0; + This.cbPending = 0; + rc = RTAsn1EncodeWrite(pRoot, RTASN1ENCODE_F_DER, rtCrPemWriteAsn1Callback, &This, pErrInfo); + AssertRCReturn(rc, rc); + cchRet += This.cchRet; + + Assert(This.cbPending <= sizeof(This.abBlock)); + if (This.cbPending) + { + size_t cchBlock = 0; + rc = RTBase64EncodeEx(This.abBlock, This.cbPending, RTBASE64_FLAGS_EOL_LF, + This.szBlock, sizeof(This.szBlock), &cchBlock); + AssertRC(rc); + This.szBlock[cchBlock++] = '\n'; + This.szBlock[cchBlock] = '\0'; + + cchRet += pfnOutput(pvUser, This.szBlock, cchBlock); + } + + /* + * -----END XXXXX----- + */ + cchRet += pfnOutput(pvUser, RT_STR_TUPLE("-----END ")); + cchRet += pfnOutput(pvUser, pszMarker, cchMarker); + cchRet += pfnOutput(pvUser, RT_STR_TUPLE("-----\n")); + + /* termination call */ + cchRet += pfnOutput(pvUser, NULL, 0); + + return cchRet; +} + + +RTDECL(ssize_t) RTCrPemWriteAsn1ToVfsIoStrm(RTVFSIOSTREAM hVfsIos, PRTASN1CORE pRoot, + uint32_t fFlags, const char *pszMarker, PRTERRINFO pErrInfo) +{ + VFSIOSTRMOUTBUF Buf; + VFSIOSTRMOUTBUF_INIT(&Buf, hVfsIos); + ssize_t cchRet = RTCrPemWriteAsn1(RTVfsIoStrmStrOutputCallback, &Buf, pRoot, fFlags, pszMarker, pErrInfo); + Assert(Buf.offBuf == 0); + return RT_SUCCESS(Buf.rc) ? (ssize_t)cchRet : Buf.rc; +} + + +RTDECL(ssize_t) RTCrPemWriteAsn1ToVfsFile(RTVFSFILE hVfsFile, PRTASN1CORE pRoot, + uint32_t fFlags, const char *pszMarker, PRTERRINFO pErrInfo) +{ + RTVFSIOSTREAM hVfsIos = RTVfsFileToIoStream(hVfsFile); + AssertReturn(hVfsIos != NIL_RTVFSIOSTREAM, VERR_INVALID_HANDLE); + ssize_t cchRet = RTCrPemWriteAsn1ToVfsIoStrm(hVfsIos, pRoot, fFlags, pszMarker, pErrInfo); + RTVfsIoStrmRelease(hVfsIos); + return cchRet; +} diff --git a/src/VBox/Runtime/common/crypto/pkcs7-asn1-decoder.cpp b/src/VBox/Runtime/common/crypto/pkcs7-asn1-decoder.cpp new file mode 100644 index 00000000..d5faef8a --- /dev/null +++ b/src/VBox/Runtime/common/crypto/pkcs7-asn1-decoder.cpp @@ -0,0 +1,174 @@ +/* $Id: pkcs7-asn1-decoder.cpp $ */ +/** @file + * IPRT - Crypto - PKCS \#7, Decoder for ASN.1. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/pkcs7.h> + +#include <iprt/err.h> +#include <iprt/string.h> +#include <iprt/crypto/spc.h> +#include <iprt/crypto/tsp.h> + +#include "pkcs7-internal.h" + + +/* + * PKCS #7 ContentInfo + */ +typedef enum RTCRPKCS7CONTENTINFOCHOICE +{ + RTCRPKCS7CONTENTINFOCHOICE_INVALID = 0, + RTCRPKCS7CONTENTINFOCHOICE_UNKNOWN, + RTCRPKCS7CONTENTINFOCHOICE_SIGNED_DATA, + RTCRPKCS7CONTENTINFOCHOICE_SPC_INDIRECT_DATA_CONTENT, + RTCRPKCS7CONTENTINFOCHOICE_TSP_TST_INFO, + RTCRPKCS7CONTENTINFOCHOICE_END, + RTCRPKCS7CONTENTINFOCHOICE_32BIT_HACK = 0x7fffffff +} RTCRPKCS7CONTENTINFOCHOICE; + +static int rtCrPkcs7ContentInfo_DecodeExtra(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTCRPKCS7CONTENTINFO pThis, + const char *pszErrorTag) +{ + RT_NOREF_PV(fFlags); RT_NOREF_PV(pszErrorTag); + pThis->u.pCore = NULL; + + /* + * Figure the type. + */ + RTCRPKCS7CONTENTINFOCHOICE enmChoice; + size_t cbContent = 0; + if (RTAsn1ObjId_CompareWithString(&pThis->ContentType, RTCRPKCS7SIGNEDDATA_OID) == 0) + { + enmChoice = RTCRPKCS7CONTENTINFOCHOICE_SIGNED_DATA; + cbContent = sizeof(*pThis->u.pSignedData); + } + else if (RTAsn1ObjId_CompareWithString(&pThis->ContentType, RTCRSPCINDIRECTDATACONTENT_OID) == 0) + { + enmChoice = RTCRPKCS7CONTENTINFOCHOICE_SPC_INDIRECT_DATA_CONTENT; + cbContent = sizeof(*pThis->u.pIndirectDataContent); + } + else if (RTAsn1ObjId_CompareWithString(&pThis->ContentType, RTCRTSPTSTINFO_OID) == 0) + { + enmChoice = RTCRPKCS7CONTENTINFOCHOICE_TSP_TST_INFO; + cbContent = sizeof(*pThis->u.pTstInfo); + } + else + { + enmChoice = RTCRPKCS7CONTENTINFOCHOICE_UNKNOWN; + cbContent = 0; + } + + int rc = VINF_SUCCESS; + if (enmChoice != RTCRPKCS7CONTENTINFOCHOICE_UNKNOWN) + { + /* + * Detect CMS octet string format and open the content cursor. + * + * Current we don't have any octent string content which, they're all + * sequences, which make detection so much simpler. + */ + PRTASN1OCTETSTRING pOctetString = &pThis->Content; + RTASN1CURSOR ContentCursor; + rc = RTAsn1CursorInitSubFromCore(pCursor, &pThis->Content.Asn1Core, &ContentCursor, "Content"); + if ( RT_SUCCESS(rc) + && RTAsn1CursorIsNextEx(&ContentCursor, ASN1_TAG_OCTET_STRING, ASN1_TAGFLAG_PRIMITIVE | ASN1_TAGCLASS_UNIVERSAL)) + { + rc = RTAsn1MemAllocZ(&pThis->Content.EncapsulatedAllocation, (void **)&pThis->Content.pEncapsulated, + sizeof(*pOctetString)); + if (RT_SUCCESS(rc)) + { + pThis->pCmsContent = pOctetString = (PRTASN1OCTETSTRING)pThis->Content.pEncapsulated; + rc = RTAsn1OctetString_DecodeAsn1(&ContentCursor, 0, pOctetString, "CmsContent"); + if (RT_SUCCESS(rc)) + rc = RTAsn1CursorCheckEnd(&ContentCursor); + if (RT_SUCCESS(rc)) + rc = RTAsn1CursorInitSubFromCore(pCursor, &pOctetString->Asn1Core, &ContentCursor, "CmsContent"); + } + } + if (RT_SUCCESS(rc)) + { + /* + * Allocate memory for the decoded content. + */ + rc = RTAsn1MemAllocZ(&pOctetString->EncapsulatedAllocation, (void **)&pOctetString->pEncapsulated, cbContent); + if (RT_SUCCESS(rc)) + { + pThis->u.pCore = pOctetString->pEncapsulated; + + /* + * Decode it. + */ + switch (enmChoice) + { + case RTCRPKCS7CONTENTINFOCHOICE_SIGNED_DATA: + rc = RTCrPkcs7SignedData_DecodeAsn1(&ContentCursor, 0, pThis->u.pSignedData, "SignedData"); + break; + case RTCRPKCS7CONTENTINFOCHOICE_SPC_INDIRECT_DATA_CONTENT: + rc = RTCrSpcIndirectDataContent_DecodeAsn1(&ContentCursor, 0, pThis->u.pIndirectDataContent, + "IndirectDataContent"); + break; + case RTCRPKCS7CONTENTINFOCHOICE_TSP_TST_INFO: + rc = RTCrTspTstInfo_DecodeAsn1(&ContentCursor, 0, pThis->u.pTstInfo, "TstInfo"); + break; + default: + AssertFailed(); + rc = VERR_IPE_NOT_REACHED_DEFAULT_CASE; + break; + } + if (RT_SUCCESS(rc)) + rc = RTAsn1CursorCheckOctStrEnd(&ContentCursor, &pThis->Content); + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + + RTAsn1MemFree(&pOctetString->EncapsulatedAllocation, pOctetString->pEncapsulated); + pOctetString->pEncapsulated = NULL; + pThis->u.pCore = NULL; + } + } + } + return rc; +} + + +/* + * Generate the code. + */ +#include <iprt/asn1-generator-asn1-decoder.h> + diff --git a/src/VBox/Runtime/common/crypto/pkcs7-core.cpp b/src/VBox/Runtime/common/crypto/pkcs7-core.cpp new file mode 100644 index 00000000..f5bdd3d8 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/pkcs7-core.cpp @@ -0,0 +1,250 @@ +/* $Id: pkcs7-core.cpp $ */ +/** @file + * IPRT - Crypto - PKCS \#7, Core APIs. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/pkcs7.h> + +#include <iprt/errcore.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/crypto/digest.h> +#include <iprt/crypto/tsp.h> + +#include "pkcs7-internal.h" + +/* + * PKCS #7 Attributes + */ + +RTDECL(int) RTCrPkcs7Attributes_HashAttributes(PRTCRPKCS7ATTRIBUTES pAttributes, RTCRDIGEST hDigest, PRTERRINFO pErrInfo) +{ + /* ASSUMES that the attributes are encoded according to DER. */ + uint8_t const *pbData; + uint32_t cbData; + void *pvFree = NULL; + int rc = RTAsn1EncodeQueryRawBits(RTCrPkcs7Attributes_GetAsn1Core(pAttributes), + &pbData, &cbData, &pvFree, pErrInfo); + if (RT_SUCCESS(rc)) + { + uint8_t bSetOfTag = ASN1_TAG_SET | ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_CONSTRUCTED; + rc = RTCrDigestUpdate(hDigest, &bSetOfTag, sizeof(bSetOfTag)); /* Replace the implict tag with a SET-OF tag. */ + if (RT_SUCCESS(rc)) + rc = RTCrDigestUpdate(hDigest, pbData + sizeof(bSetOfTag), cbData - sizeof(bSetOfTag)); /* Skip the implicit tag. */ + if (RT_SUCCESS(rc)) + rc = RTCrDigestFinal(hDigest, NULL, 0); + else + RTErrInfoSet(pErrInfo, rc, "RTCrDigestUpdate failed"); + RTMemTmpFree(pvFree); + } + return rc; +} + + +/* + * PKCS #7 SignerInfo + */ + +RTDECL(PCRTASN1TIME) RTCrPkcs7SignerInfo_GetSigningTime(PCRTCRPKCS7SIGNERINFO pThis, PCRTCRPKCS7SIGNERINFO *ppSignerInfo) +{ + /* + * Check the immediate level, unless we're continuing a previous search. + * Note! We ASSUME a single signing time attribute, which simplifies the interface. + */ + uint32_t cAttrsLeft; + PRTCRPKCS7ATTRIBUTE const *ppAttr; + if (!ppSignerInfo || *ppSignerInfo == NULL) + { + cAttrsLeft = pThis->AuthenticatedAttributes.cItems; + ppAttr = pThis->AuthenticatedAttributes.papItems; + while (cAttrsLeft-- > 0) + { + PCRTCRPKCS7ATTRIBUTE pAttr = *ppAttr; + if ( pAttr->enmType == RTCRPKCS7ATTRIBUTETYPE_SIGNING_TIME + && pAttr->uValues.pSigningTime->cItems > 0) + { + if (ppSignerInfo) + *ppSignerInfo = pThis; + return pAttr->uValues.pSigningTime->papItems[0]; + } + ppAttr++; + } + } + else if (*ppSignerInfo == pThis) + *ppSignerInfo = NULL; + + /* + * Check counter signatures. + */ + cAttrsLeft = pThis->UnauthenticatedAttributes.cItems; + ppAttr = pThis->UnauthenticatedAttributes.papItems; + while (cAttrsLeft-- > 0) + { + PCRTCRPKCS7ATTRIBUTE pAttr = *ppAttr; + if (pAttr->enmType == RTCRPKCS7ATTRIBUTETYPE_COUNTER_SIGNATURES) + { + uint32_t cSignatures = pAttr->uValues.pCounterSignatures->cItems; + PRTCRPKCS7SIGNERINFO *ppSignature = pAttr->uValues.pCounterSignatures->papItems; + + /* Skip past the previous counter signature. */ + if (ppSignerInfo && *ppSignerInfo != NULL) + while (cSignatures > 0) + { + cSignatures--; + if (*ppSignature == *ppSignerInfo) + { + *ppSignerInfo = NULL; + ppSignature++; + break; + } + ppSignature++; + } + + /* Search the counter signatures (if any remaining). */ + while (cSignatures-- > 0) + { + PCRTCRPKCS7SIGNERINFO pSignature = *ppSignature; + uint32_t cCounterAttrsLeft = pSignature->AuthenticatedAttributes.cItems; + PRTCRPKCS7ATTRIBUTE const *ppCounterAttr = pSignature->AuthenticatedAttributes.papItems; + while (cCounterAttrsLeft-- > 0) + { + PCRTCRPKCS7ATTRIBUTE pCounterAttr = *ppCounterAttr; + if ( pCounterAttr->enmType == RTCRPKCS7ATTRIBUTETYPE_SIGNING_TIME + && pCounterAttr->uValues.pSigningTime->cItems > 0) + { + if (ppSignerInfo) + *ppSignerInfo = pSignature; + return pCounterAttr->uValues.pSigningTime->papItems[0]; + } + ppCounterAttr++; + } + ppSignature++; + } + } + ppAttr++; + } + + /* + * No signing timestamp found. + */ + if (ppSignerInfo) + *ppSignerInfo = NULL; + + return NULL; +} + + +RTDECL(PCRTASN1TIME) RTCrPkcs7SignerInfo_GetMsTimestamp(PCRTCRPKCS7SIGNERINFO pThis, PCRTCRPKCS7CONTENTINFO *ppContentInfoRet) +{ + /* + * Assume there is only one, so no need to enumerate anything here. + */ + uint32_t cAttrsLeft = pThis->UnauthenticatedAttributes.cItems; + PRTCRPKCS7ATTRIBUTE const *ppAttr = pThis->UnauthenticatedAttributes.papItems; + while (cAttrsLeft-- > 0) + { + PCRTCRPKCS7ATTRIBUTE pAttr = *ppAttr; + if (pAttr->enmType == RTCRPKCS7ATTRIBUTETYPE_MS_TIMESTAMP) + { + uint32_t cLeft = pAttr->uValues.pContentInfos->cItems; + PRTCRPKCS7CONTENTINFO const *ppContentInfo = pAttr->uValues.pContentInfos->papItems; + while (cLeft-- > 0) + { + PCRTCRPKCS7CONTENTINFO pContentInfo = *ppContentInfo; + if (RTAsn1ObjId_CompareWithString(&pContentInfo->ContentType, RTCRPKCS7SIGNEDDATA_OID) == 0) + { + if (RTAsn1ObjId_CompareWithString(&pContentInfo->u.pSignedData->ContentInfo.ContentType, + RTCRTSPTSTINFO_OID) == 0) + { + if (ppContentInfoRet) + *ppContentInfoRet = pContentInfo; + return &pContentInfo->u.pSignedData->ContentInfo.u.pTstInfo->GenTime; + } + } + + pContentInfo++; + } + } + ppAttr++; + } + + /* + * No signature was found. + */ + if (ppContentInfoRet) + *ppContentInfoRet = NULL; + + return NULL; +} + + +/* + * PKCS #7 ContentInfo. + */ + +RTDECL(bool) RTCrPkcs7ContentInfo_IsSignedData(PCRTCRPKCS7CONTENTINFO pThis) +{ + return RTAsn1ObjId_CompareWithString(&pThis->ContentType, RTCRPKCS7SIGNEDDATA_OID) == 0; +} + + +/* + * Set of some kind of certificate supported by PKCS #7 or CMS. + */ + +RTDECL(PCRTCRX509CERTIFICATE) +RTCrPkcs7SetOfCerts_FindX509ByIssuerAndSerialNumber(PCRTCRPKCS7SETOFCERTS pCertificates, + PCRTCRX509NAME pIssuer, PCRTASN1INTEGER pSerialNumber) +{ + for (uint32_t i = 0; i < pCertificates->cItems; i++) + { + PCRTCRPKCS7CERT pCert = pCertificates->papItems[i]; + if ( pCert->enmChoice == RTCRPKCS7CERTCHOICE_X509 + && RTCrX509Certificate_MatchIssuerAndSerialNumber(pCert->u.pX509Cert, pIssuer, pSerialNumber)) + return pCert->u.pX509Cert; + } + return NULL; +} + + +/* + * Generate the standard core code. + */ +#include <iprt/asn1-generator-core.h> + diff --git a/src/VBox/Runtime/common/crypto/pkcs7-file.cpp b/src/VBox/Runtime/common/crypto/pkcs7-file.cpp new file mode 100644 index 00000000..b8e8b9ff --- /dev/null +++ b/src/VBox/Runtime/common/crypto/pkcs7-file.cpp @@ -0,0 +1,119 @@ +/* $Id: pkcs7-file.cpp $ */ +/** @file + * IPRT - Crypto - PKCS\#7/CMS, File related APIs. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/pkcs7.h> + +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/crypto/pem.h> + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static RTCRPEMMARKERWORD const g_aWords_Cms[] = { { RT_STR_TUPLE("CMS") } }; +static RTCRPEMMARKERWORD const g_aWords_Pkcs7[] = { { RT_STR_TUPLE("PKCS7") } }; +/** X509 Certificate markers. */ +RT_DECL_DATA_CONST(RTCRPEMMARKER const) g_aRTCrPkcs7Markers[] = +{ + { g_aWords_Cms, RT_ELEMENTS(g_aWords_Cms) }, + { g_aWords_Pkcs7, RT_ELEMENTS(g_aWords_Pkcs7) } +}; +/** Number of entries in g_aRTCrPkcs7Markers. */ +RT_DECL_DATA_CONST(uint32_t const) g_cRTCrPkcs7Markers = RT_ELEMENTS(g_aRTCrPkcs7Markers); + + +/** @name Flags for RTCrPkcs7ContentInfo_ReadFromBuffer + * @{ */ +/** Only allow PEM certificates, not binary ones. + * @sa RTCRPEMREADFILE_F_ONLY_PEM */ +#define RTCRPKCS7_READ_F_PEM_ONLY RT_BIT(1) +/** @} */ + + +RTDECL(int) RTCrPkcs7_ReadFromBuffer(PRTCRPKCS7CONTENTINFO pContentInfo, const void *pvBuf, size_t cbBuf, + uint32_t fFlags, PCRTASN1ALLOCATORVTABLE pAllocator, + bool *pfCmsLabeled, PRTERRINFO pErrInfo, const char *pszErrorTag) +{ + if (pfCmsLabeled) + *pfCmsLabeled = false; + AssertReturn(!(fFlags & ~RTCRX509CERT_READ_F_PEM_ONLY), VERR_INVALID_FLAGS); + + PCRTCRPEMSECTION pSectionHead; + int rc = RTCrPemParseContent(pvBuf, cbBuf, + fFlags & RTCRX509CERT_READ_F_PEM_ONLY ? RTCRPEMREADFILE_F_ONLY_PEM : 0, + g_aRTCrPkcs7Markers, g_cRTCrPkcs7Markers, + &pSectionHead, pErrInfo); + if (RT_SUCCESS(rc)) + { + if (pSectionHead) + { + if (pfCmsLabeled) + *pfCmsLabeled = pSectionHead->pMarker == &g_aRTCrPkcs7Markers[0]; + + RTASN1CURSORPRIMARY PrimaryCursor; + RTAsn1CursorInitPrimary(&PrimaryCursor, pSectionHead->pbData, (uint32_t)RT_MIN(pSectionHead->cbData, UINT32_MAX), + pErrInfo, pAllocator, RTASN1CURSOR_FLAGS_DER, pszErrorTag); + + RTCRPKCS7CONTENTINFO TmpContentInfo; + rc = RTCrPkcs7ContentInfo_DecodeAsn1(&PrimaryCursor.Cursor, 0, &TmpContentInfo, "CI"); + if (RT_SUCCESS(rc)) + { + rc = RTCrPkcs7ContentInfo_CheckSanity(&TmpContentInfo, 0, pErrInfo, "CI"); + if (RT_SUCCESS(rc)) + { + rc = RTCrPkcs7ContentInfo_Clone(pContentInfo, &TmpContentInfo, pAllocator); + if (RT_SUCCESS(rc)) + { + if (pSectionHead->pNext || PrimaryCursor.Cursor.cbLeft) + rc = VINF_ASN1_MORE_DATA; + } + } + RTCrPkcs7ContentInfo_Delete(&TmpContentInfo); + } + RTCrPemFreeSections(pSectionHead); + } + else + rc = rc != VINF_SUCCESS ? -rc : VERR_INTERNAL_ERROR_2; + } + return rc; +} + diff --git a/src/VBox/Runtime/common/crypto/pkcs7-init.cpp b/src/VBox/Runtime/common/crypto/pkcs7-init.cpp new file mode 100644 index 00000000..f2bd7942 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/pkcs7-init.cpp @@ -0,0 +1,62 @@ +/* $Id: pkcs7-init.cpp $ */ +/** @file + * IPRT - Crypto - PKCS \#7, Initialization API. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/pkcs7.h> + +#include <iprt/errcore.h> +#include <iprt/string.h> +#include <iprt/uni.h> + +#include "pkcs7-internal.h" + + +static int rtCrPkcs7ContentInfo_CloneExtra(PRTCRPKCS7CONTENTINFO pThis) +{ + pThis->u.pCore = pThis->Content.pEncapsulated; + return VINF_SUCCESS; +} + + +/* + * Generate the code. + */ +#include <iprt/asn1-generator-init.h> + diff --git a/src/VBox/Runtime/common/crypto/pkcs7-internal.h b/src/VBox/Runtime/common/crypto/pkcs7-internal.h new file mode 100644 index 00000000..8d285f74 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/pkcs7-internal.h @@ -0,0 +1,47 @@ +/* $Id: pkcs7-internal.h $ */ +/** @file + * IPRT - Crypto - PKCS \#7, Internal Header. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef IPRT_INCLUDED_SRC_common_crypto_pkcs7_internal_h +#define IPRT_INCLUDED_SRC_common_crypto_pkcs7_internal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#define RTASN1TMPL_TEMPLATE_FILE "../common/crypto/pkcs7-template.h" +#include <iprt/asn1-generator-internal-header.h> + +#endif /* !IPRT_INCLUDED_SRC_common_crypto_pkcs7_internal_h */ + diff --git a/src/VBox/Runtime/common/crypto/pkcs7-sanity.cpp b/src/VBox/Runtime/common/crypto/pkcs7-sanity.cpp new file mode 100644 index 00000000..ebc60a1a --- /dev/null +++ b/src/VBox/Runtime/common/crypto/pkcs7-sanity.cpp @@ -0,0 +1,219 @@ +/* $Id: pkcs7-sanity.cpp $ */ +/** @file + * IPRT - Crypto - PKCS \#7, Sanity Checkers. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/pkcs7.h> + +#include <iprt/err.h> +#include <iprt/string.h> + +//#include <iprt/stream.h> + +#include "pkcs7-internal.h" + + +static int rtCrPkcs7SignedData_CheckSanityExtra(PCRTCRPKCS7SIGNEDDATA pSignedData, uint32_t fFlags, + PRTERRINFO pErrInfo, const char *pszErrorTag) +{ + bool const fAuthenticode = RT_BOOL(fFlags & RTCRPKCS7SIGNEDDATA_SANITY_F_AUTHENTICODE); + RT_NOREF_PV(fFlags); + + //RTAsn1Dump(&pSignedData->SeqCore.Asn1Core, 0, 0, RTAsn1DumpStrmPrintfV, g_pStdOut); + + if ( RTAsn1Integer_UnsignedCompareWithU32(&pSignedData->Version, RTCRPKCS7SIGNEDDATA_V1) != 0 + && RTAsn1Integer_UnsignedCompareWithU32(&pSignedData->Version, RTCRPKCS7SIGNEDDATA_V3) != 0 + && RTAsn1Integer_UnsignedCompareWithU32(&pSignedData->Version, RTCRPKCS7SIGNEDDATA_V4) != 0 + && RTAsn1Integer_UnsignedCompareWithU32(&pSignedData->Version, RTCRPKCS7SIGNEDDATA_V5) != 0) + return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_SIGNED_DATA_VERSION, "SignedData version is %llu, expected %u", + pSignedData->Version.uValue.u, RTCRPKCS7SIGNEDDATA_V1); + + /* + * DigestAlgorithms. + */ + if (pSignedData->DigestAlgorithms.cItems == 0) /** @todo this might be too strict */ + return RTErrInfoSet(pErrInfo, VERR_CR_PKCS7_SIGNED_DATA_NO_DIGEST_ALGOS, "SignedData.DigestAlgorithms is empty"); + if (pSignedData->DigestAlgorithms.cItems != 1 && fAuthenticode) + return RTErrInfoSetF(pErrInfo, VERR_CR_SPC_NOT_EXACTLY_ONE_DIGEST_ALGO, + "%s: SignedData.DigestAlgorithms has more than one algorithm (%u)", + pszErrorTag, pSignedData->DigestAlgorithms.cItems); + + if (fFlags & RTCRPKCS7SIGNEDDATA_SANITY_F_ONLY_KNOWN_HASH) + for (uint32_t i = 0; i < pSignedData->DigestAlgorithms.cItems; i++) + { + if (RTCrX509AlgorithmIdentifier_QueryDigestType(pSignedData->DigestAlgorithms.papItems[i]) == RTDIGESTTYPE_INVALID) + return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_UNKNOWN_DIGEST_ALGORITHM, + "%s: SignedData.DigestAlgorithms[%i] is not known: %s", + pszErrorTag, i, pSignedData->DigestAlgorithms.papItems[i]->Algorithm.szObjId); + if ( pSignedData->DigestAlgorithms.papItems[i]->Parameters.enmType != RTASN1TYPE_NULL + && pSignedData->DigestAlgorithms.papItems[i]->Parameters.enmType != RTASN1TYPE_NOT_PRESENT) + return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_DIGEST_PARAMS_NOT_IMPL, + "%s: SignedData.DigestAlgorithms[%i] has parameters: tag=%u", + pszErrorTag, i, pSignedData->DigestAlgorithms.papItems[i]->Parameters.u.Core.uTag); + } + + /* + * Certificates. + */ + if ( (fFlags & RTCRPKCS7SIGNEDDATA_SANITY_F_SIGNING_CERT_PRESENT) + && pSignedData->Certificates.cItems == 0) + return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_NO_CERTIFICATES, + "%s: SignedData.Certifcates is empty, expected at least one certificate", pszErrorTag); + + /* + * Crls. + */ + if (fAuthenticode && RTAsn1Core_IsPresent(&pSignedData->Crls)) + return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_EXPECTED_NO_CRLS, + "%s: SignedData.Crls is not empty as expected for authenticode.", pszErrorTag); + /** @todo check Crls when they become important. */ + + /* + * SignerInfos. + */ + if (pSignedData->SignerInfos.cItems == 0) + return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_NO_SIGNER_INFOS, "%s: SignedData.SignerInfos is empty?", pszErrorTag); + if (fAuthenticode && pSignedData->SignerInfos.cItems != 1) + return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_EXPECTED_ONE_SIGNER_INFO, + "%s: SignedData.SignerInfos should have one entry for authenticode: %u", + pszErrorTag, pSignedData->SignerInfos.cItems); + + for (uint32_t i = 0; i < pSignedData->SignerInfos.cItems; i++) + { + PCRTCRPKCS7SIGNERINFO pSignerInfo = pSignedData->SignerInfos.papItems[i]; + + if (RTAsn1Integer_UnsignedCompareWithU32(&pSignerInfo->Version, RTCRPKCS7SIGNERINFO_V1) != 0) + return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_SIGNER_INFO_VERSION, + "%s: SignedData.SignerInfos[%u] version is %llu, expected %u", + pszErrorTag, i, pSignerInfo->Version.uValue.u, RTCRPKCS7SIGNERINFO_V1); + + /* IssuerAndSerialNumber. */ + int rc = RTCrX509Name_CheckSanity(&pSignerInfo->IssuerAndSerialNumber.Name, 0, pErrInfo, + "SignedData.SignerInfos[#].IssuerAndSerialNumber.Name"); + if (RT_FAILURE(rc)) + return rc; + + if (pSignerInfo->IssuerAndSerialNumber.SerialNumber.Asn1Core.cb == 0) + return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_SIGNER_INFO_NO_ISSUER_SERIAL_NO, + "%s: SignedData.SignerInfos[%u].IssuerAndSerialNumber.SerialNumber is missing (zero length)", + pszErrorTag, i); + + PCRTCRX509CERTIFICATE pCert; + pCert = RTCrPkcs7SetOfCerts_FindX509ByIssuerAndSerialNumber(&pSignedData->Certificates, + &pSignerInfo->IssuerAndSerialNumber.Name, + &pSignerInfo->IssuerAndSerialNumber.SerialNumber); + if (!pCert && (fFlags & RTCRPKCS7SIGNEDDATA_SANITY_F_SIGNING_CERT_PRESENT)) + return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_SIGNER_CERT_NOT_SHIPPED, + "%s: SignedData.SignerInfos[%u].IssuerAndSerialNumber not found in T0.Certificates", + pszErrorTag, i); + + /* DigestAlgorithm */ + uint32_t j = 0; + while ( j < pSignedData->DigestAlgorithms.cItems + && RTCrX509AlgorithmIdentifier_Compare(pSignedData->DigestAlgorithms.papItems[j], + &pSignerInfo->DigestAlgorithm) != 0) + j++; + if (j >= pSignedData->DigestAlgorithms.cItems) + return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_DIGEST_ALGO_NOT_FOUND_IN_LIST, + "%s: SignedData.SignerInfos[%u].DigestAlgorithm (%s) not found in SignedData.DigestAlgorithms", + pszErrorTag, i, pSignerInfo->DigestAlgorithm.Algorithm.szObjId); + + /* Digest encryption algorithm. */ +#if 0 /** @todo Unimportant: Seen timestamp signatures specifying pkcs1-Sha256WithRsaEncryption in SignerInfo and just RSA in the certificate. Figure out how to compare the two. */ + if ( pCert + && RTCrX509AlgorithmIdentifier_Compare(&pSignerInfo->DigestEncryptionAlgorithm, + &pCert->TbsCertificate.SubjectPublicKeyInfo.Algorithm) != 0) + return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_SIGNER_INFO_DIGEST_ENCRYPT_MISMATCH, + "SignedData.SignerInfos[%u].DigestEncryptionAlgorithm (%s) mismatch with certificate (%s)", + i, pSignerInfo->DigestEncryptionAlgorithm.Algorithm.szObjId, + pCert->TbsCertificate.SubjectPublicKeyInfo.Algorithm.Algorithm.szObjId); +#endif + + /* Authenticated attributes we know. */ + if (RTCrPkcs7Attributes_IsPresent(&pSignerInfo->AuthenticatedAttributes)) + { + bool fFoundContentInfo = false; + bool fFoundMessageDigest = false; + for (j = 0; j < pSignerInfo->AuthenticatedAttributes.cItems; j++) + { + PCRTCRPKCS7ATTRIBUTE pAttrib = pSignerInfo->AuthenticatedAttributes.papItems[j]; + if (RTAsn1ObjId_CompareWithString(&pAttrib->Type, RTCR_PKCS9_ID_CONTENT_TYPE_OID) == 0) + { + if (fFoundContentInfo) + return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_MISSING_CONTENT_TYPE_ATTRIB, + "%s: Multiple authenticated content-type attributes.", pszErrorTag); + fFoundContentInfo = true; + AssertReturn(pAttrib->enmType == RTCRPKCS7ATTRIBUTETYPE_OBJ_IDS, VERR_INTERNAL_ERROR_3); + if (pAttrib->uValues.pObjIds->cItems != 1) + return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_BAD_CONTENT_TYPE_ATTRIB, + "%s: Expected exactly one value for content-type attrib, found: %u", + pszErrorTag, pAttrib->uValues.pObjIds->cItems); + } + else if (RTAsn1ObjId_CompareWithString(&pAttrib->Type, RTCR_PKCS9_ID_MESSAGE_DIGEST_OID) == 0) + { + if (fFoundMessageDigest) + return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_MISSING_MESSAGE_DIGEST_ATTRIB, + "%s: Multiple authenticated message-digest attributes.", pszErrorTag); + fFoundMessageDigest = true; + AssertReturn(pAttrib->enmType == RTCRPKCS7ATTRIBUTETYPE_OCTET_STRINGS, VERR_INTERNAL_ERROR_3); + if (pAttrib->uValues.pOctetStrings->cItems != 1) + return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_BAD_CONTENT_TYPE_ATTRIB, + "%s: Expected exactly one value for message-digest attrib, found: %u", + pszErrorTag, pAttrib->uValues.pOctetStrings->cItems); + } + } + + if (!fFoundContentInfo) + return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_MISSING_CONTENT_TYPE_ATTRIB, + "%s: Missing authenticated content-type attribute.", pszErrorTag); + if (!fFoundMessageDigest) + return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_MISSING_MESSAGE_DIGEST_ATTRIB, + "%s: Missing authenticated message-digest attribute.", pszErrorTag); + } + } + + return VINF_SUCCESS; +} + + +/* + * Generate the code. + */ +#include <iprt/asn1-generator-sanity.h> + diff --git a/src/VBox/Runtime/common/crypto/pkcs7-sign.cpp b/src/VBox/Runtime/common/crypto/pkcs7-sign.cpp new file mode 100644 index 00000000..2a700c14 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/pkcs7-sign.cpp @@ -0,0 +1,592 @@ +/* $Id: pkcs7-sign.cpp $ */ +/** @file + * IPRT - Crypto - PKCS \#7, Signing + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/pkcs7.h> + +#include <iprt/err.h> +#include <iprt/string.h> +#include <iprt/crypto/digest.h> +#include <iprt/crypto/key.h> +#include <iprt/crypto/pkix.h> +#include <iprt/crypto/store.h> +#include <iprt/crypto/x509.h> + +#ifdef IPRT_WITH_OPENSSL +# include "internal/iprt-openssl.h" +# include "internal/openssl-pre.h" +# include <openssl/asn1t.h> +# include <openssl/pkcs7.h> +# include <openssl/cms.h> +# include <openssl/x509.h> +# include <openssl/err.h> +# include "internal/openssl-post.h" +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * PKCS\#7 / CMS signing operation instance. + */ +typedef struct RTCRPKCS7SIGNINGJOBINT +{ + /** Magic value (RTCRPKCS7SIGNINGJOBINT). */ + uint32_t u32Magic; + /** Reference counter. */ + uint32_t volatile cRefs; + /** RTCRPKCS7SIGN_F_XXX. */ + uint64_t fFlags; + /** Set if finalized. */ + bool fFinallized; + + //.... +} RTCRPKCS7SIGNINGJOBINT; + +/** Magic value for RTCRPKCS7SIGNINGJOBINT (Jonathan Lethem). */ +#define RTCRPKCS7SIGNINGJOBINT_MAGIC UINT32_C(0x19640219) + +/** Handle to PKCS\#7/CMS signing operation. */ +typedef struct RTCRPKCS7SIGNINGJOBINT *RTCRPKCS7SIGNINGJOB; +/** Pointer to a PKCS\#7/CMS signing operation handle. */ +typedef RTCRPKCS7SIGNINGJOB *PRTCRPKCS7SIGNINGJOB; + +//// CMS_sign +//RTDECL(int) RTCrPkcs7Sign(PRTCRPKCS7SIGNINGJOB *phJob, uint64_t fFlags, PCRTCRX509CERTIFICATE pSigner, RTCRKEY hPrivateKey, +// RTCRSTORE hAdditionalCerts, +// + +#ifdef IPRT_WITH_OPENSSL + +static int rtCrPkcs7SimpleSignSignedDataDoV1TweakContent(PKCS7 *pOsslPkcs7, const char *pszContentId, + const void *pvData, size_t cbData, + PRTERRINFO pErrInfo) +{ + AssertReturn(pszContentId, RTErrInfoSet(pErrInfo, VERR_CR_PKCS7_MISSING_CONTENT_TYPE_ATTRIB, + "RTCRPKCS7SIGN_SD_F_NO_DATA_ENCAP requires content type in additional attribs")); + + /* + * Create a new inner PKCS#7 content container, forcing it to the 'other' type. + */ + PKCS7 *pOsslInnerContent = PKCS7_new(); + if (!pOsslInnerContent) + return RTErrInfoSet(pErrInfo, VERR_NO_MEMORY, "PKCS7_new failed"); + + /* Set the type. */ + int rc; + pOsslInnerContent->type = OBJ_txt2obj(pszContentId, 1); + if (pOsslInnerContent->type) + { + /* Create a dynamic ASN1 type which we set to a sequence. */ + ASN1_TYPE *pOsslOther = pOsslInnerContent->d.other = ASN1_TYPE_new(); + if (pOsslOther) + { + pOsslOther->type = V_ASN1_SEQUENCE; + + /* Create a string and put the data in it. */ + ASN1_STRING *pOsslStr = pOsslOther->value.sequence = ASN1_STRING_new(); + if (pOsslStr) + { + rc = ASN1_STRING_set(pOsslStr, pvData, (int)cbData); /* copies the buffer content */ + if (rc > 0) + { + /* + * Set the content in the PKCS#7 signed data we're constructing. + * This consumes pOsslInnerContent on success. + */ + rc = PKCS7_set_content(pOsslPkcs7, pOsslInnerContent); + if (rc > 0) + return VINF_SUCCESS; + + /* failed */ + rc = RTErrInfoSet(pErrInfo, VERR_NO_MEMORY, "PKCS7_set_content"); + } + else + rc = RTErrInfoSetF(pErrInfo, VERR_NO_MEMORY, "ASN1_STRING_set(,,%#x)", cbData); + } + else + rc = RTErrInfoSet(pErrInfo, VERR_NO_MEMORY, "ASN1_STRING_new"); + } + else + rc = RTErrInfoSet(pErrInfo, VERR_NO_MEMORY, "ASN1_TYPE_new"); + } + else + rc = RTErrInfoSetF(pErrInfo, VERR_NO_MEMORY, "OBJ_txt2obj(%s, 1) failed", pszContentId); + PKCS7_free(pOsslInnerContent); + return rc; +} + + +static int rtCrPkcs7SimpleSignSignedDataDoV1TweakedFinal(PKCS7 *pOsslPkcs7, const char *pszContentId, + const void *pvData, size_t cbData, PRTERRINFO pErrInfo) +{ + AssertReturn(pszContentId, RTErrInfoSet(pErrInfo, VERR_CR_PKCS7_MISSING_CONTENT_TYPE_ATTRIB, + "RTCRPKCS7SIGN_SD_F_NO_DATA_ENCAP requires content type in additional attribs")); + + /* + * Prepare a BIO of what should be hashed with all the hashing filters attached. + */ + BIO *pOsslBio = PKCS7_dataInit(pOsslPkcs7, NULL); + if (!pOsslBio) + return RTErrInfoSet(pErrInfo, VERR_CR_CIPHER_OSSL_ENCRYPT_FINAL_FAILED, "PKCS7_dataInit failed"); + + /* + * Now write the data. + * + * We must skip the outer wrapper here (see RTCrPkcs7VerifySignedData). This + * is probably a bit presumptive about what we're working on, so add an extra + * flag for this later. + */ + uint8_t const *pbToWrite = (uint8_t const *)pvData; + size_t cbToWrite = cbData; + + /** @todo add extra flag for this? */ + RTASN1CURSORPRIMARY SkipCursor; + RTAsn1CursorInitPrimary(&SkipCursor, pvData, (uint32_t)cbData, + pErrInfo,&g_RTAsn1DefaultAllocator, RTASN1CURSOR_FLAGS_DER, "skip"); + RTASN1CORE SkipAsn1Core = { 0 }; + int rc = RTAsn1CursorReadHdr(&SkipCursor.Cursor, &SkipAsn1Core, "skip-core"); + if (RT_SUCCESS(rc)) + { + pbToWrite += SkipAsn1Core.cbHdr; + cbToWrite -= SkipAsn1Core.cbHdr; + + rc = BIO_write(pOsslBio, pbToWrite, (int)cbToWrite); + if (rc == (ssize_t)cbToWrite) + { + BIO_flush(pOsslBio); /** @todo error check this */ + if (true) + { + /* + * Finalize the job - produce the signer info signatures and stuff. + */ + rc = PKCS7_dataFinal(pOsslPkcs7, pOsslBio); + if (rc > 0) + { + /* + * Now tweak the content so we get the desired content type and + * no extra wrappers and stuff. + */ + rc = rtCrPkcs7SimpleSignSignedDataDoV1TweakContent(pOsslPkcs7, pszContentId, pvData, cbData, pErrInfo); + } + else + rc = RTErrInfoSetF(pErrInfo, VERR_CR_CIPHER_OSSL_ENCRYPT_FINAL_FAILED, "PKCS7_dataFinal failed: %d", rc); + } + } + else + rc = RTErrInfoSetF(pErrInfo, VERR_CR_CIPHER_OSSL_ENCRYPT_FINAL_FAILED, + "%zu byte data write failed: %d", cbToWrite, rc); + } + BIO_free_all(pOsslBio); + return rc; +} + + +static int rtCrPkcs7SimpleSignSignedDataDoV1AttribConversion(PKCS7_SIGNER_INFO *pSignerInfo, + PCRTCRPKCS7ATTRIBUTES pAdditionalAuthenticatedAttribs, + const char **ppszContentId, PRTERRINFO pErrInfo) +{ + int rc = VINF_SUCCESS; + *ppszContentId = NULL; + + if (pAdditionalAuthenticatedAttribs) + { + + /* + * Convert each attribute. + */ + STACK_OF(X509_ATTRIBUTE) *pOsslAttributes = sk_X509_ATTRIBUTE_new_null(); + for (uint32_t i = 0; i < pAdditionalAuthenticatedAttribs->cItems && RT_SUCCESS(rc); i++) + { + PCRTCRPKCS7ATTRIBUTE pAttrib = pAdditionalAuthenticatedAttribs->papItems[i]; + + /* Look out for content type, as we will probably need that for + RTCRPKCS7SIGN_SD_F_NO_DATA_ENCAP later. */ + if ( pAttrib->enmType == RTCRPKCS7ATTRIBUTETYPE_OBJ_IDS + && RTAsn1ObjId_CompareWithString(&pAttrib->Type, RTCR_PKCS9_ID_CONTENT_TYPE_OID) == 0) + { + AssertBreakStmt(!*ppszContentId, rc = VERR_CR_PKCS7_BAD_CONTENT_TYPE_ATTRIB); + AssertBreakStmt(pAttrib->uValues.pObjIds && pAttrib->uValues.pObjIds->cItems == 1, + rc = VERR_CR_PKCS7_BAD_CONTENT_TYPE_ATTRIB); + *ppszContentId = pAttrib->uValues.pObjIds->papItems[0]->szObjId; + } + + /* The conversion (IPRT encode, OpenSSL decode). */ + X509_ATTRIBUTE *pOsslAttrib; + rc = rtCrOpenSslConvertPkcs7Attribute((void **)&pOsslAttrib, pAttrib, pErrInfo); + if (RT_SUCCESS(rc)) + { + if (!sk_X509_ATTRIBUTE_push(pOsslAttributes, pOsslAttrib)) + rc = RTErrInfoSet(pErrInfo, VERR_NO_MEMORY, "sk_X509_ATTRIBUTE_push failed"); + } + } + + /* + * If we've successfully converted all the attributes, make a deep copy + * (waste of resource, but whatever) into the signer info we're working on. + */ + if (RT_SUCCESS(rc)) + { + rc = PKCS7_set_signed_attributes(pSignerInfo, pOsslAttributes); /* deep copy */ + if (rc <= 0) + rc = RTErrInfoSet(pErrInfo, VERR_NO_MEMORY, "PKCS7_set_signed_attributes failed"); + } + + /* + * Free the attributes (they were copied). Cannot use X509_ATTRIBUTE_pop_free as + * the callback causes Visual C++ to complain about exceptions on the callback. + */ + for (int i = sk_X509_ATTRIBUTE_num(pOsslAttributes) - 1; i >= 0; i--) + X509_ATTRIBUTE_free(sk_X509_ATTRIBUTE_value(pOsslAttributes, i)); + sk_X509_ATTRIBUTE_free(pOsslAttributes); + } + return rc; +} + +static int rtCrPkcs7SimpleSignSignedDataDoV1(uint32_t fFlags, X509 *pOsslSigner, EVP_PKEY *pEvpPrivateKey, + BIO *pOsslData, const EVP_MD *pEvpMd, STACK_OF(X509) *pOsslAdditionalCerts, + PCRTCRPKCS7ATTRIBUTES pAdditionalAuthenticatedAttribs, + const void *pvData, size_t cbData, + BIO **ppOsslResult, PRTERRINFO pErrInfo) +{ + /* + * Use PKCS7_sign with PKCS7_PARTIAL to start a extended the signing process. + */ + /* Create a ContentInfo we can modify using CMS_sign w/ CMS_PARTIAL. */ + unsigned int fOsslSign = PKCS7_BINARY | PKCS7_PARTIAL; + if (fFlags & RTCRPKCS7SIGN_SD_F_DEATCHED) + fOsslSign |= PKCS7_DETACHED; + if (fFlags & RTCRPKCS7SIGN_SD_F_NO_SMIME_CAP) + fOsslSign |= PKCS7_NOSMIMECAP; + int rc = VINF_SUCCESS; + PKCS7 *pCms = PKCS7_sign(NULL, NULL, pOsslAdditionalCerts, NULL, fOsslSign); + if (pCms != NULL) + { + /* + * Add a signer. + */ + PKCS7_SIGNER_INFO *pSignerInfo = PKCS7_sign_add_signer(pCms, pOsslSigner, pEvpPrivateKey, pEvpMd, fOsslSign); + if (pSignerInfo) + { + /* + * Add additional attributes to the signer. + */ + const char *pszContentId = NULL; + rc = rtCrPkcs7SimpleSignSignedDataDoV1AttribConversion(pSignerInfo, pAdditionalAuthenticatedAttribs, + &pszContentId, pErrInfo); + if (RT_SUCCESS(rc)) + { + /* + * Finalized and actually sign the data. + */ + bool const fTweaked = (fFlags & (RTCRPKCS7SIGN_SD_F_DEATCHED | RTCRPKCS7SIGN_SD_F_NO_DATA_ENCAP)) + == RTCRPKCS7SIGN_SD_F_NO_DATA_ENCAP; + if (fTweaked) + rc = rtCrPkcs7SimpleSignSignedDataDoV1TweakedFinal(pCms, pszContentId, pvData, cbData, pErrInfo); + else + { + rc = PKCS7_final(pCms, pOsslData, fOsslSign); + if (rc > 0) + rc = VINF_SUCCESS; + else + rc = RTErrInfoSet(pErrInfo, VERR_GENERAL_FAILURE, "PKCS7_final"); + /** @todo maybe we want to use rtCrPkcs7SimpleSignSignedDataDoV1TweakContent + * for when the content type isn't 'data'... */ + } + if (RT_SUCCESS(rc)) + { + /* + * Get the output and copy it into the result buffer. + */ + BIO *pOsslResult = BIO_new(BIO_s_mem()); + if (pOsslResult) + { + rc = i2d_PKCS7_bio(pOsslResult, pCms); + if (rc > 0) + { + *ppOsslResult = pOsslResult; + rc = VINF_SUCCESS; + } + else + { + rc = RTErrInfoSet(pErrInfo, VERR_GENERAL_FAILURE, "i2d_PKCS7_bio"); + BIO_free(pOsslResult); + } + } + else + rc = RTErrInfoSet(pErrInfo, VERR_NO_MEMORY, "BIO_new/BIO_s_mem"); + } + } + else + rc = RTErrInfoSet(pErrInfo, VERR_GENERAL_FAILURE, "PKCS7_sign_add_signer"); + } + PKCS7_free(pCms); + } + else + rc = RTErrInfoSet(pErrInfo, VERR_GENERAL_FAILURE, "PKCS7_sign"); + return rc; +} + + +static int rtCrPkcs7SimpleSignSignedDataDoDefault(uint32_t fFlags, X509 *pOsslSigner, EVP_PKEY *pEvpPrivateKey, + BIO *pOsslData, const EVP_MD *pEvpMd, STACK_OF(X509) *pOsslAdditionalCerts, + PCRTCRPKCS7ATTRIBUTES pAdditionalAuthenticatedAttribs, + BIO **ppOsslResult, PRTERRINFO pErrInfo) + +{ + /* + * Use CMS_sign with CMS_PARTIAL to start a extended the signing process. + */ + /* Create a ContentInfo we can modify using CMS_sign w/ CMS_PARTIAL. */ + unsigned int fOsslSign = CMS_BINARY | CMS_PARTIAL; + if (fFlags & RTCRPKCS7SIGN_SD_F_DEATCHED) + fOsslSign |= CMS_DETACHED; + if (fFlags & RTCRPKCS7SIGN_SD_F_NO_SMIME_CAP) + fOsslSign |= CMS_NOSMIMECAP; + int rc = VINF_SUCCESS; + CMS_ContentInfo *pCms = CMS_sign(NULL, NULL, pOsslAdditionalCerts, NULL, fOsslSign); + if (pCms != NULL) + { + /* + * Set encapsulated content type if present in the auth attribs. + */ + uint32_t iAuthAttrSkip = UINT32_MAX; + for (uint32_t i = 0; i < pAdditionalAuthenticatedAttribs->cItems && RT_SUCCESS(rc); i++) + { + PCRTCRPKCS7ATTRIBUTE pAttrib = pAdditionalAuthenticatedAttribs->papItems[i]; + if ( pAttrib->enmType == RTCRPKCS7ATTRIBUTETYPE_OBJ_IDS + && RTAsn1ObjId_CompareWithString(&pAttrib->Type, RTCR_PKCS9_ID_CONTENT_TYPE_OID) == 0) + { + AssertBreakStmt(pAttrib->uValues.pObjIds && pAttrib->uValues.pObjIds->cItems == 1, + rc = VERR_INTERNAL_ERROR_3); + PCRTASN1OBJID pObjId = pAttrib->uValues.pObjIds->papItems[0]; + ASN1_OBJECT *pOsslObjId = OBJ_txt2obj(pObjId->szObjId, 0 /*no_name*/); + if (pOsslObjId) + { + rc = CMS_set1_eContentType(pCms, pOsslObjId); + ASN1_OBJECT_free(pOsslObjId); + if (rc < 0) + rc = RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_GENERIC_ERROR, + "CMS_set1_eContentType(%s)", pObjId->szObjId); + } + else + rc = RTErrInfoSet(pErrInfo, VERR_NO_MEMORY, "OBJ_txt2obj"); + + iAuthAttrSkip = i; + break; + } + } + if (RT_SUCCESS(rc)) + { + /* + * Add a signer. + */ + CMS_SignerInfo *pSignerInfo = CMS_add1_signer(pCms, pOsslSigner, pEvpPrivateKey, pEvpMd, fOsslSign); + if (pSignerInfo) + { + /* + * Add additional attributes, skipping the content type if found above. + */ + if (pAdditionalAuthenticatedAttribs) + for (uint32_t i = 0; i < pAdditionalAuthenticatedAttribs->cItems && RT_SUCCESS(rc); i++) + if (i != iAuthAttrSkip) + { + PCRTCRPKCS7ATTRIBUTE pAttrib = pAdditionalAuthenticatedAttribs->papItems[i]; + X509_ATTRIBUTE *pOsslAttrib; + rc = rtCrOpenSslConvertPkcs7Attribute((void **)&pOsslAttrib, pAttrib, pErrInfo); + if (RT_SUCCESS(rc)) + { + rc = CMS_signed_add1_attr(pSignerInfo, pOsslAttrib); + rtCrOpenSslFreeConvertedPkcs7Attribute((void **)pOsslAttrib); + if (rc <= 0) + rc = RTErrInfoSet(pErrInfo, VERR_NO_MEMORY, "CMS_signed_add1_attr"); + } + } + if (RT_SUCCESS(rc)) + { + /* + * Finalized and actually sign the data. + */ + rc = CMS_final(pCms, pOsslData, NULL /*dcont*/, fOsslSign); + if (rc > 0) + { + /* + * Get the output and copy it into the result buffer. + */ + BIO *pOsslResult = BIO_new(BIO_s_mem()); + if (pOsslResult) + { + rc = i2d_CMS_bio(pOsslResult, pCms); + if (rc > 0) + { + *ppOsslResult = pOsslResult; + rc = VINF_SUCCESS; + } + else + { + rc = RTErrInfoSet(pErrInfo, VERR_GENERAL_FAILURE, "i2d_CMS_bio"); + BIO_free(pOsslResult); + } + } + else + rc = RTErrInfoSet(pErrInfo, VERR_NO_MEMORY, "BIO_new/BIO_s_mem"); + } + else + rc = RTErrInfoSet(pErrInfo, VERR_GENERAL_FAILURE, "CMS_final"); + } + } + else + rc = RTErrInfoSet(pErrInfo, VERR_GENERAL_FAILURE, "CMS_add1_signer"); + } + CMS_ContentInfo_free(pCms); + } + else + rc = RTErrInfoSet(pErrInfo, VERR_GENERAL_FAILURE, "CMS_sign"); + return rc; +} + +#endif /* IPRT_WITH_OPENSSL */ + + + +RTDECL(int) RTCrPkcs7SimpleSignSignedData(uint32_t fFlags, PCRTCRX509CERTIFICATE pSigner, RTCRKEY hPrivateKey, + void const *pvData, size_t cbData, RTDIGESTTYPE enmDigestType, + RTCRSTORE hAdditionalCerts, PCRTCRPKCS7ATTRIBUTES pAdditionalAuthenticatedAttribs, + void *pvResult, size_t *pcbResult, PRTERRINFO pErrInfo) +{ + size_t const cbResultBuf = *pcbResult; + *pcbResult = 0; + AssertReturn(!(fFlags & ~RTCRPKCS7SIGN_SD_F_VALID_MASK), VERR_INVALID_FLAGS); +#ifdef IPRT_WITH_OPENSSL + AssertReturn((int)cbData >= 0 && (unsigned)cbData == cbData, VERR_TOO_MUCH_DATA); + + /* + * Resolve the digest type. + */ + const EVP_MD *pEvpMd = NULL; + if (enmDigestType != RTDIGESTTYPE_UNKNOWN) + { + pEvpMd = (const EVP_MD *)rtCrOpenSslConvertDigestType(enmDigestType, pErrInfo); + AssertReturn(pEvpMd, pErrInfo ? pErrInfo->rc : VERR_INVALID_PARAMETER); + } + + /* + * Convert the private key. + */ + EVP_PKEY *pEvpPrivateKey = NULL; + int rc = rtCrKeyToOpenSslKey(hPrivateKey, false /*fNeedPublic*/, (void **)&pEvpPrivateKey, pErrInfo); + if (RT_SUCCESS(rc)) + { + /* + * Convert the signing certificate. + */ + X509 *pOsslSigner = NULL; + rc = rtCrOpenSslConvertX509Cert((void **)&pOsslSigner, pSigner, pErrInfo); + if (RT_SUCCESS(rc)) + { + /* + * Convert any additional certificates. + */ + STACK_OF(X509) *pOsslAdditionalCerts = NULL; + if (hAdditionalCerts != NIL_RTCRSTORE) + rc = RTCrStoreConvertToOpenSslCertStack(hAdditionalCerts, 0 /*fFlags*/, (void **)&pOsslAdditionalCerts, pErrInfo); + if (RT_SUCCESS(rc)) + { + /* + * Create a BIO for the data buffer. + */ + BIO *pOsslData = BIO_new_mem_buf((void *)pvData, (int)cbData); + if (pOsslData) + { + /* + * Do the work. + */ + BIO *pOsslResult = NULL; + if (!(fFlags & RTCRPKCS7SIGN_SD_F_USE_V1)) + rc = rtCrPkcs7SimpleSignSignedDataDoDefault(fFlags, pOsslSigner, pEvpPrivateKey, pOsslData, pEvpMd, + pOsslAdditionalCerts, pAdditionalAuthenticatedAttribs, + &pOsslResult, pErrInfo); + else + rc = rtCrPkcs7SimpleSignSignedDataDoV1(fFlags, pOsslSigner, pEvpPrivateKey, pOsslData, pEvpMd, + pOsslAdditionalCerts, pAdditionalAuthenticatedAttribs, + pvData, cbData, + &pOsslResult, pErrInfo); + BIO_free(pOsslData); + if (RT_SUCCESS(rc)) + { + /* + * Copy out the result. + */ + BUF_MEM *pBuf = NULL; + rc = (int)BIO_get_mem_ptr(pOsslResult, &pBuf); + if (rc > 0) + { + AssertPtr(pBuf); + size_t const cbResult = pBuf->length; + if ( cbResultBuf >= cbResult + && pvResult != NULL) + { + memcpy(pvResult, pBuf->data, cbResult); + rc = VINF_SUCCESS; + } + else + rc = VERR_BUFFER_OVERFLOW; + *pcbResult = cbResult; + } + else + rc = RTErrInfoSet(pErrInfo, VERR_GENERAL_FAILURE, "BIO_get_mem_ptr"); + BIO_free(pOsslResult); + } + } + } + rtCrOpenSslFreeConvertedX509Cert(pOsslSigner); + } + EVP_PKEY_free(pEvpPrivateKey); + } + return rc; +#else + RT_NOREF(fFlags, pSigner, hPrivateKey, pvData, cbData, enmDigestType, hAdditionalCerts, pAdditionalAuthenticatedAttribs, + pvResult, pErrInfo, cbResultBuf); + *pcbResult = 0; + return VERR_NOT_IMPLEMENTED; +#endif +} + diff --git a/src/VBox/Runtime/common/crypto/pkcs7-template.h b/src/VBox/Runtime/common/crypto/pkcs7-template.h new file mode 100644 index 00000000..c976a1c0 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/pkcs7-template.h @@ -0,0 +1,236 @@ +/* $Id: pkcs7-template.h $ */ +/** @file + * IPRT - Crypto - PKCS \#7, Core APIs, Code Generator Template. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#define RTASN1TMPL_DECL RTDECL + +/* + * One PCKS #7 IssuerAndSerialNumber. + */ +#define RTASN1TMPL_TYPE RTCRPKCS7ISSUERANDSERIALNUMBER +#define RTASN1TMPL_EXT_NAME RTCrPkcs7IssuerAndSerialNumber +#define RTASN1TMPL_INT_NAME rtCrPkcs7IssuerAndSerialNumber +RTASN1TMPL_BEGIN_SEQCORE(); +RTASN1TMPL_MEMBER( Name, RTCRX509NAME, RTCrX509Name); +RTASN1TMPL_MEMBER( SerialNumber, RTASN1INTEGER, RTAsn1Integer); +RTASN1TMPL_END_SEQCORE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * One PCKS #7 Attribute. + */ +#define RTASN1TMPL_TYPE RTCRPKCS7ATTRIBUTE +#define RTASN1TMPL_EXT_NAME RTCrPkcs7Attribute +#define RTASN1TMPL_INT_NAME rtCrPkcs7Attribute +RTASN1TMPL_BEGIN_SEQCORE(); +RTASN1TMPL_MEMBER( Type, RTASN1OBJID, RTAsn1ObjId); +RTASN1TMPL_MEMBER_DYN_BEGIN(Type, RTCRPKCS7ATTRIBUTETYPE, enmType, Allocation); +RTASN1TMPL_MEMBER_DYN( uValues, pOctetStrings, MessageDigest, RTASN1SETOFOCTETSTRINGS, RTAsn1SetOfOctetStrings, Allocation, + Type, enmType, RTCRPKCS7ATTRIBUTETYPE_OCTET_STRINGS, RTCR_PKCS9_ID_MESSAGE_DIGEST_OID); +RTASN1TMPL_MEMBER_DYN( uValues, pObjIds, ContentType, RTASN1SETOFOBJIDS, RTAsn1SetOfObjIds, Allocation, + Type, enmType, RTCRPKCS7ATTRIBUTETYPE_OBJ_IDS, RTCR_PKCS9_ID_CONTENT_TYPE_OID); +RTASN1TMPL_MEMBER_DYN( uValues, pCounterSignatures,CounterSignatures, RTCRPKCS7SIGNERINFOS, RTCrPkcs7SignerInfos, Allocation, + Type, enmType, RTCRPKCS7ATTRIBUTETYPE_COUNTER_SIGNATURES, RTCR_PKCS9_ID_COUNTER_SIGNATURE_OID); +RTASN1TMPL_MEMBER_DYN( uValues, pSigningTime, SigningTime, RTASN1SETOFTIMES, RTAsn1SetOfTimes, Allocation, + Type, enmType, RTCRPKCS7ATTRIBUTETYPE_SIGNING_TIME, RTCR_PKCS9_ID_SIGNING_TIME_OID); +RTASN1TMPL_MEMBER_DYN( uValues, pContentInfos, MsTimestamp, RTCRPKCS7SETOFCONTENTINFOS, RTCrPkcs7SetOfContentInfos, Allocation, + Type, enmType, RTCRPKCS7ATTRIBUTETYPE_MS_TIMESTAMP, RTCR_PKCS9_ID_MS_TIMESTAMP); +RTASN1TMPL_MEMBER_DYN( uValues, pContentInfos, MsNestedSignature, RTCRPKCS7SETOFCONTENTINFOS, RTCrPkcs7SetOfContentInfos, Allocation, + Type, enmType, RTCRPKCS7ATTRIBUTETYPE_MS_NESTED_SIGNATURE, RTCR_PKCS9_ID_MS_NESTED_SIGNATURE); +RTASN1TMPL_MEMBER_DYN( uValues, pObjIdSeqs, MsStatementType, RTASN1SETOFOBJIDSEQS, RTAsn1SetOfObjIdSeqs, Allocation, + Type, enmType, RTCRPKCS7ATTRIBUTETYPE_MS_STATEMENT_TYPE, RTCR_PKCS9_ID_MS_STATEMENT_TYPE); +RTASN1TMPL_MEMBER_DYN( uValues, pOctetStrings, AppleMultiCdPlist, RTASN1SETOFOCTETSTRINGS, RTAsn1SetOfOctetStrings, Allocation, + Type, enmType, RTCRPKCS7ATTRIBUTETYPE_APPLE_MULTI_CD_PLIST, RTCR_PKCS9_ID_APPLE_MULTI_CD_PLIST); +RTASN1TMPL_MEMBER_DYN_DEFAULT( uValues, pCores, RTASN1SETOFCORES, RTAsn1SetOfCores, Allocation, + Type, enmType, RTCRPKCS7ATTRIBUTETYPE_UNKNOWN); +RTASN1TMPL_MEMBER_DYN_END(Type, RTCRPKCS7ATTRIBUTETYPE, enmType, Allocation); +RTASN1TMPL_END_SEQCORE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * Set of PCKS #7 Attributes. + */ +#define RTASN1TMPL_TYPE RTCRPKCS7ATTRIBUTES +#define RTASN1TMPL_EXT_NAME RTCrPkcs7Attributes +#define RTASN1TMPL_INT_NAME rtCrPkcs7Attributes +RTASN1TMPL_SET_OF(RTCRPKCS7ATTRIBUTE, RTCrPkcs7Attribute); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * One PCKS #7 SignerInfo. + */ +#define RTASN1TMPL_TYPE RTCRPKCS7SIGNERINFO +#define RTASN1TMPL_EXT_NAME RTCrPkcs7SignerInfo +#define RTASN1TMPL_INT_NAME rtCrPkcs7SignerInfo +RTASN1TMPL_BEGIN_SEQCORE(); +RTASN1TMPL_MEMBER( Version, RTASN1INTEGER, RTAsn1Integer); +RTASN1TMPL_MEMBER( IssuerAndSerialNumber, RTCRPKCS7ISSUERANDSERIALNUMBER, RTCrPkcs7IssuerAndSerialNumber); +RTASN1TMPL_MEMBER( DigestAlgorithm, RTCRX509ALGORITHMIDENTIFIER, RTCrX509AlgorithmIdentifier); +RTASN1TMPL_MEMBER_OPT_ITAG( AuthenticatedAttributes, RTCRPKCS7ATTRIBUTES, RTCrPkcs7Attributes, 0); +RTASN1TMPL_MEMBER( DigestEncryptionAlgorithm, RTCRX509ALGORITHMIDENTIFIER, RTCrX509AlgorithmIdentifier); +RTASN1TMPL_MEMBER( EncryptedDigest, RTASN1OCTETSTRING, RTAsn1OctetString); +RTASN1TMPL_MEMBER_OPT_ITAG( UnauthenticatedAttributes, RTCRPKCS7ATTRIBUTES, RTCrPkcs7Attributes, 1); +RTASN1TMPL_END_SEQCORE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * Set of PCKS #7 SignerInfos. + */ +#define RTASN1TMPL_TYPE RTCRPKCS7SIGNERINFOS +#define RTASN1TMPL_EXT_NAME RTCrPkcs7SignerInfos +#define RTASN1TMPL_INT_NAME rtCrPkcs7SignerInfos +RTASN1TMPL_SET_OF(RTCRPKCS7SIGNERINFO, RTCrPkcs7SignerInfo); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * PCKS #7 SignedData. + */ +#define RTASN1TMPL_TYPE RTCRPKCS7SIGNEDDATA +#define RTASN1TMPL_EXT_NAME RTCrPkcs7SignedData +#define RTASN1TMPL_INT_NAME rtCrPkcs7SignedData +RTASN1TMPL_BEGIN_SEQCORE(); +RTASN1TMPL_MEMBER( Version, RTASN1INTEGER, RTAsn1Integer); +RTASN1TMPL_MEMBER( DigestAlgorithms, RTCRX509ALGORITHMIDENTIFIERS, RTCrX509AlgorithmIdentifiers); +RTASN1TMPL_MEMBER( ContentInfo, RTCRPKCS7CONTENTINFO, RTCrPkcs7ContentInfo); +RTASN1TMPL_MEMBER_OPT_ITAG( Certificates, RTCRPKCS7SETOFCERTS, RTCrPkcs7SetOfCerts, 0); +RTASN1TMPL_MEMBER_OPT_ITAG( Crls, RTASN1CORE, RTAsn1Core, 1); +RTASN1TMPL_MEMBER( SignerInfos, RTCRPKCS7SIGNERINFOS, RTCrPkcs7SignerInfos); +RTASN1TMPL_EXEC_CHECK_SANITY( rc = rtCrPkcs7SignedData_CheckSanityExtra(pThis, fFlags, pErrInfo, pszErrorTag) ) /* no ; */ +RTASN1TMPL_END_SEQCORE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * Set of PCKS #7 SignedData. + */ +#define RTASN1TMPL_TYPE RTCRPKCS7SETOFSIGNEDDATA +#define RTASN1TMPL_EXT_NAME RTCrPkcs7SetOfSignedData +#define RTASN1TMPL_INT_NAME rtCrPkcs7SetOfSignedData +RTASN1TMPL_SET_OF(RTCRPKCS7SIGNEDDATA, RTCrPkcs7SignedData); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * PCKS #7 DigestInfo. + */ +#define RTASN1TMPL_TYPE RTCRPKCS7DIGESTINFO +#define RTASN1TMPL_EXT_NAME RTCrPkcs7DigestInfo +#define RTASN1TMPL_INT_NAME rtCrPkcs7DigestInfo +RTASN1TMPL_BEGIN_SEQCORE(); +RTASN1TMPL_MEMBER( DigestAlgorithm, RTCRX509ALGORITHMIDENTIFIER, RTCrX509AlgorithmIdentifier); +RTASN1TMPL_MEMBER( Digest, RTASN1OCTETSTRING, RTAsn1OctetString); +RTASN1TMPL_END_SEQCORE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * PCKS #7 ContentInfo. + */ +#define RTASN1TMPL_TYPE RTCRPKCS7CONTENTINFO +#define RTASN1TMPL_EXT_NAME RTCrPkcs7ContentInfo +#define RTASN1TMPL_INT_NAME rtCrPkcs7ContentInfo +RTASN1TMPL_BEGIN_SEQCORE(); +RTASN1TMPL_MEMBER( ContentType, RTASN1OBJID, RTAsn1ObjId); +RTASN1TMPL_MEMBER_OPT_ITAG( Content, RTASN1OCTETSTRING, RTAsn1OctetString, 0); +RTASN1TMPL_EXEC_DECODE( rc = rtCrPkcs7ContentInfo_DecodeExtra(pCursor, fFlags, pThis, pszErrorTag)) /* no ; */ +RTASN1TMPL_EXEC_CLONE( rc = rtCrPkcs7ContentInfo_CloneExtra(pThis) ) /* no ; */ +RTASN1TMPL_END_SEQCORE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * Set of PCKS #7 ContentInfo. + */ +#define RTASN1TMPL_TYPE RTCRPKCS7SETOFCONTENTINFOS +#define RTASN1TMPL_EXT_NAME RTCrPkcs7SetOfContentInfos +#define RTASN1TMPL_INT_NAME rtCrPkcs7SetOfContentInfos +RTASN1TMPL_SET_OF(RTCRPKCS7CONTENTINFO, RTCrPkcs7ContentInfo); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * One PKCS #7 ExtendedCertificateOrCertificate or a CMS CertificateChoices (sic). + */ +#define RTASN1TMPL_TYPE RTCRPKCS7CERT +#define RTASN1TMPL_EXT_NAME RTCrPkcs7Cert +#define RTASN1TMPL_INT_NAME rtCrPkcs7Cert +RTASN1TMPL_BEGIN_PCHOICE(); +RTASN1TMPL_PCHOICE_ITAG_UC( ASN1_TAG_SEQUENCE, RTCRPKCS7CERTCHOICE_X509, u.pX509Cert, X509Cert, RTCRX509CERTIFICATE, RTCrX509Certificate); +RTASN1TMPL_PCHOICE_ITAG( 0, RTCRPKCS7CERTCHOICE_EXTENDED_PKCS6, u.pExtendedCert, ExtendedCert, RTASN1CORE, RTAsn1Core); +RTASN1TMPL_PCHOICE_ITAG( 1, RTCRPKCS7CERTCHOICE_AC_V1, u.pAcV1, AcV1, RTASN1CORE, RTAsn1Core); +RTASN1TMPL_PCHOICE_ITAG( 2, RTCRPKCS7CERTCHOICE_AC_V2, u.pAcV2, AcV2, RTASN1CORE, RTAsn1Core); +RTASN1TMPL_PCHOICE_ITAG( 3, RTCRPKCS7CERTCHOICE_OTHER, u.pOtherCert, OtherCert, RTASN1CORE, RTAsn1Core); +RTASN1TMPL_END_PCHOICE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * Set of PKCS #7 ExtendedCertificateOrCertificate or a CMS CertificateChoices. + */ +#define RTASN1TMPL_TYPE RTCRPKCS7SETOFCERTS +#define RTASN1TMPL_EXT_NAME RTCrPkcs7SetOfCerts +#define RTASN1TMPL_INT_NAME rtCrPkcs7SetOfCerts +RTASN1TMPL_SET_OF(RTCRPKCS7CERT, RTCrPkcs7Cert); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + diff --git a/src/VBox/Runtime/common/crypto/pkcs7-verify.cpp b/src/VBox/Runtime/common/crypto/pkcs7-verify.cpp new file mode 100644 index 00000000..7f00ae57 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/pkcs7-verify.cpp @@ -0,0 +1,866 @@ +/* $Id: pkcs7-verify.cpp $ */ +/** @file + * IPRT - Crypto - PKCS \#7, Verification + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/pkcs7.h> + +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/crypto/digest.h> +#include <iprt/crypto/key.h> +#include <iprt/crypto/pkix.h> +#include <iprt/crypto/store.h> +#include <iprt/crypto/x509.h> + +#ifdef IPRT_WITH_OPENSSL +# include "internal/iprt-openssl.h" +# include "internal/openssl-pre.h" +# include <openssl/pkcs7.h> +# include <openssl/x509.h> +# include <openssl/err.h> +# include "internal/openssl-post.h" +#endif + + + +#ifdef IPRT_WITH_OPENSSL +static int rtCrPkcs7VerifySignedDataUsingOpenSsl(PCRTCRPKCS7CONTENTINFO pContentInfo, uint32_t fFlags, + RTCRSTORE hAdditionalCerts, RTCRSTORE hTrustedCerts, + void const *pvContent, size_t cbContent, PRTERRINFO pErrInfo) +{ + RT_NOREF_PV(fFlags); + + /* + * Verify using OpenSSL. ERR_PUT_error + */ + unsigned char const *pbRawContent; + uint32_t cbRawContent; + void *pvFree; + int rcOssl = RTAsn1EncodeQueryRawBits(RTCrPkcs7ContentInfo_GetAsn1Core(pContentInfo), + (const uint8_t **)&pbRawContent, &cbRawContent, &pvFree, pErrInfo); + AssertRCReturn(rcOssl, rcOssl); + + PKCS7 *pOsslPkcs7 = NULL; + PKCS7 *pOsslPkcs7Ret = d2i_PKCS7(&pOsslPkcs7, &pbRawContent, cbRawContent); + + RTMemTmpFree(pvFree); + + if (pOsslPkcs7 != NULL && pOsslPkcs7Ret == pOsslPkcs7) + { + STACK_OF(X509) *pAddCerts = NULL; + if (hAdditionalCerts != NIL_RTCRSTORE) + rcOssl = RTCrStoreConvertToOpenSslCertStack(hAdditionalCerts, 0, (void **)&pAddCerts, pErrInfo); + else + { + pAddCerts = sk_X509_new_null(); + rcOssl = RT_LIKELY(pAddCerts != NULL) ? VINF_SUCCESS : VERR_NO_MEMORY; + } + if (RT_SUCCESS(rcOssl)) + { + PCRTCRPKCS7SETOFCERTS pCerts = &pContentInfo->u.pSignedData->Certificates; + for (uint32_t i = 0; i < pCerts->cItems; i++) + if (pCerts->papItems[i]->enmChoice == RTCRPKCS7CERTCHOICE_X509) + rtCrOpenSslAddX509CertToStack(pAddCerts, pCerts->papItems[i]->u.pX509Cert, NULL); + + X509_STORE *pTrustedCerts = NULL; + if (hTrustedCerts != NIL_RTCRSTORE) + rcOssl = RTCrStoreConvertToOpenSslCertStore(hTrustedCerts, 0, (void **)&pTrustedCerts, pErrInfo); + if (RT_SUCCESS(rcOssl)) + { + rtCrOpenSslInit(); + + BIO *pBioContent = BIO_new_mem_buf((void *)pvContent, (int)cbContent); + if (pBioContent) + { + uint32_t fOsslFlags = PKCS7_NOCHAIN; + fOsslFlags |= PKCS7_NOVERIFY; // temporary hack. + if (PKCS7_verify(pOsslPkcs7, pAddCerts, pTrustedCerts, pBioContent, NULL /*out*/, fOsslFlags)) + rcOssl = VINF_SUCCESS; + else + { + rcOssl = RTErrInfoSet(pErrInfo, VERR_CR_PKCS7_OSSL_VERIFY_FAILED, "PKCS7_verify failed: "); + if (pErrInfo) + ERR_print_errors_cb(rtCrOpenSslErrInfoCallback, pErrInfo); + } + BIO_free(pBioContent); + } + if (pTrustedCerts) + X509_STORE_free(pTrustedCerts); + } + else + rcOssl = RTErrInfoSet(pErrInfo, rcOssl, "RTCrStoreConvertToOpenSslCertStore failed"); +#include "internal/openssl-pre.h" /* Need to disable C5039 warning here. */ + if (pAddCerts) + sk_X509_pop_free(pAddCerts, X509_free); +#include "internal/openssl-post.h" + } + else + rcOssl = RTErrInfoSet(pErrInfo, rcOssl, "RTCrStoreConvertToOpenSslCertStack failed"); + PKCS7_free(pOsslPkcs7); + } + else + { + rcOssl = RTErrInfoSet(pErrInfo, VERR_CR_PKCS7_OSSL_D2I_FAILED, "d2i_PKCS7 failed"); + if (pErrInfo) + ERR_print_errors_cb(rtCrOpenSslErrInfoCallback, pErrInfo); + } + + return rcOssl; +} +#endif /* IPRT_WITH_OPENSSL */ + + + +static int rtCrPkcs7VerifyCertUsageTimstamping(PCRTCRX509CERTIFICATE pCert, PRTERRINFO pErrInfo) +{ + if (!(pCert->TbsCertificate.T3.fFlags & RTCRX509TBSCERTIFICATE_F_PRESENT_EXT_KEY_USAGE)) + return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_KEY_USAGE_MISMATCH, "No extended key usage certificate attribute."); + if (!(pCert->TbsCertificate.T3.fExtKeyUsage & (RTCRX509CERT_EKU_F_TIMESTAMPING | RTCRX509CERT_EKU_F_MS_TIMESTAMP_SIGNING))) + return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_KEY_USAGE_MISMATCH, "fExtKeyUsage=%#x, missing %#x (time stamping)", + pCert->TbsCertificate.T3.fExtKeyUsage, + RTCRX509CERT_EKU_F_TIMESTAMPING | RTCRX509CERT_EKU_F_MS_TIMESTAMP_SIGNING); + return VINF_SUCCESS; +} + + +static int rtCrPkcs7VerifyCertUsageDigitalSignature(PCRTCRX509CERTIFICATE pCert, PRTERRINFO pErrInfo) +{ + if ( (pCert->TbsCertificate.T3.fFlags & RTCRX509TBSCERTIFICATE_F_PRESENT_KEY_USAGE) + && !(pCert->TbsCertificate.T3.fKeyUsage & RTCRX509CERT_KEY_USAGE_F_DIGITAL_SIGNATURE)) + return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_KEY_USAGE_MISMATCH, "fKeyUsage=%#x, missing %#x", + pCert->TbsCertificate.T3.fKeyUsage, RTCRX509CERT_KEY_USAGE_F_DIGITAL_SIGNATURE); + return VINF_SUCCESS; +} + + +/** + * @callback_method_impl{RTCRPKCS7VERIFYCERTCALLBACK, + * Default implementation that checks for the DigitalSignature KeyUsage bit.} + */ +RTDECL(int) RTCrPkcs7VerifyCertCallbackDefault(PCRTCRX509CERTIFICATE pCert, RTCRX509CERTPATHS hCertPaths, uint32_t fFlags, + void *pvUser, PRTERRINFO pErrInfo) +{ + RT_NOREF_PV(hCertPaths); RT_NOREF_PV(pvUser); + int rc = VINF_SUCCESS; + + if (fFlags & RTCRPKCS7VCC_F_SIGNED_DATA) + rc = rtCrPkcs7VerifyCertUsageDigitalSignature(pCert, pErrInfo); + + if ( (fFlags & RTCRPKCS7VCC_F_TIMESTAMP) + && RT_SUCCESS(rc)) + rc = rtCrPkcs7VerifyCertUsageTimstamping(pCert, pErrInfo); + + return rc; +} + + +/** + * @callback_method_impl{RTCRPKCS7VERIFYCERTCALLBACK, + * Standard code signing. Use this for Microsoft SPC.} + */ +RTDECL(int) RTCrPkcs7VerifyCertCallbackCodeSigning(PCRTCRX509CERTIFICATE pCert, RTCRX509CERTPATHS hCertPaths, uint32_t fFlags, + void *pvUser, PRTERRINFO pErrInfo) +{ + RT_NOREF_PV(hCertPaths); RT_NOREF_PV(pvUser); + int rc = VINF_SUCCESS; + if (fFlags & RTCRPKCS7VCC_F_SIGNED_DATA) + { + /* + * If KeyUsage is present it must include digital signature. + */ + rc = rtCrPkcs7VerifyCertUsageDigitalSignature(pCert, pErrInfo); + if (RT_SUCCESS(rc)) + { + /* + * The extended usage 'code signing' must be present. + */ + if (!(pCert->TbsCertificate.T3.fFlags & RTCRX509TBSCERTIFICATE_F_PRESENT_EXT_KEY_USAGE)) + return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_KEY_USAGE_MISMATCH, "No extended key usage certificate attribute."); + if (!(pCert->TbsCertificate.T3.fExtKeyUsage & RTCRX509CERT_EKU_F_CODE_SIGNING)) + return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_KEY_USAGE_MISMATCH, "fExtKeyUsage=%#RX64, missing CODE_SIGNING (%#RX64)", + pCert->TbsCertificate.T3.fExtKeyUsage, RTCRX509CERT_EKU_F_CODE_SIGNING); + } + } + + /* + * Timestamping too? + */ + if ( (fFlags & RTCRPKCS7VCC_F_TIMESTAMP) + && RT_SUCCESS(rc)) + rc = rtCrPkcs7VerifyCertUsageTimstamping(pCert, pErrInfo); + + return rc; +} + + +/** + * Deals with authenticated attributes. + * + * When authenticated attributes are present (checked by caller) we must: + * - fish out the content type and check it against the content inof, + * - fish out the message digest among and check it against *phDigest, + * - compute the message digest of the authenticated attributes and + * replace *phDigest with this for the signature verification. + * + * @returns IPRT status code. + * @param pSignerInfo The signer info being verified. + * @param pSignedData The signed data. + * @param phDigest On input this is the digest of the content. On + * output it will (on success) be a reference to + * the message digest of the authenticated + * attributes. The input reference is consumed. + * The caller shall release the output reference. + * @param fFlags Flags. + * @param pErrInfo Extended error info, optional. + */ +static int rtCrPkcs7VerifySignerInfoAuthAttribs(PCRTCRPKCS7SIGNERINFO pSignerInfo, PCRTCRPKCS7SIGNEDDATA pSignedData, + PRTCRDIGEST phDigest, uint32_t fFlags, PRTERRINFO pErrInfo) +{ + /* + * Scan the attributes and validate the two required attributes + * (RFC-2315, chapter 9.2, fourth bullet). Checking that we've got exactly + * one of each of them is checked by the santiy checker function, so we'll + * just assert that it did it's job here. + */ + uint32_t cContentTypes = 0; + uint32_t cMessageDigests = 0; + uint32_t i = pSignerInfo->AuthenticatedAttributes.cItems; + while (i-- > 0) + { + PCRTCRPKCS7ATTRIBUTE pAttrib = pSignerInfo->AuthenticatedAttributes.papItems[i]; + + if (RTAsn1ObjId_CompareWithString(&pAttrib->Type, RTCR_PKCS9_ID_CONTENT_TYPE_OID) == 0) + { + AssertReturn(!cContentTypes, VERR_CR_PKCS7_INTERNAL_ERROR); + AssertReturn(pAttrib->enmType == RTCRPKCS7ATTRIBUTETYPE_OBJ_IDS, VERR_CR_PKCS7_INTERNAL_ERROR); + AssertReturn(pAttrib->uValues.pObjIds->cItems == 1, VERR_CR_PKCS7_INTERNAL_ERROR); + + if ( !(fFlags & RTCRPKCS7VERIFY_SD_F_COUNTER_SIGNATURE) /* See note about microsoft below. */ + && RTAsn1ObjId_Compare(pAttrib->uValues.pObjIds->papItems[0], &pSignedData->ContentInfo.ContentType) != 0) + return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_CONTENT_TYPE_ATTRIB_MISMATCH, + "Expected content-type %s, found %s", pAttrib->uValues.pObjIds->papItems[0]->szObjId, + pSignedData->ContentInfo.ContentType.szObjId); + cContentTypes++; + } + else if (RTAsn1ObjId_CompareWithString(&pAttrib->Type, RTCR_PKCS9_ID_MESSAGE_DIGEST_OID) == 0) + { + AssertReturn(!cMessageDigests, VERR_CR_PKCS7_INTERNAL_ERROR); + AssertReturn(pAttrib->enmType == RTCRPKCS7ATTRIBUTETYPE_OCTET_STRINGS, VERR_CR_PKCS7_INTERNAL_ERROR); + AssertReturn(pAttrib->uValues.pOctetStrings->cItems == 1, VERR_CR_PKCS7_INTERNAL_ERROR); + + if (!RTCrDigestMatch(*phDigest, + pAttrib->uValues.pOctetStrings->papItems[0]->Asn1Core.uData.pv, + pAttrib->uValues.pOctetStrings->papItems[0]->Asn1Core.cb)) + { + size_t cbHash = RTCrDigestGetHashSize(*phDigest); + if (cbHash != pAttrib->uValues.pOctetStrings->papItems[0]->Asn1Core.cb) + return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_MESSAGE_DIGEST_ATTRIB_MISMATCH, + "Authenticated message-digest attribute mismatch: cbHash=%#zx cbValue=%#x", + cbHash, pAttrib->uValues.pOctetStrings->papItems[0]->Asn1Core.cb); + return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_MESSAGE_DIGEST_ATTRIB_MISMATCH, + "Authenticated message-digest attribute mismatch (cbHash=%#zx):\n" + "signed: %.*Rhxs\n" + "our: %.*Rhxs\n", + cbHash, + cbHash, pAttrib->uValues.pOctetStrings->papItems[0]->Asn1Core.uData.pv, + cbHash, RTCrDigestGetHash(*phDigest)); + } + cMessageDigests++; + } + } + + /* + * Full error reporting here as we don't currently extensively santiy check + * counter signatures. + * Note! Microsoft includes content info in their timestamp counter signatures, + * at least for vista, despite the RFC-3852 stating counter signatures + * "MUST NOT contain a content-type". + */ + if (RT_UNLIKELY( cContentTypes != 1 + && !(fFlags & RTCRPKCS7VERIFY_SD_F_COUNTER_SIGNATURE))) + return RTErrInfoSet(pErrInfo, VERR_CR_PKCS7_MISSING_CONTENT_TYPE_ATTRIB, + "Missing authenticated content-type attribute."); + if (RT_UNLIKELY(cMessageDigests != 1)) + return RTErrInfoSet(pErrInfo, VERR_CR_PKCS7_MISSING_MESSAGE_DIGEST_ATTRIB, + "Missing authenticated message-digest attribute."); + + /* + * Calculate the digest of the authenticated attributes for use in the + * signature validation. + */ + if ( pSignerInfo->DigestAlgorithm.Parameters.enmType != RTASN1TYPE_NULL + && pSignerInfo->DigestAlgorithm.Parameters.enmType != RTASN1TYPE_NOT_PRESENT) + return RTErrInfoSet(pErrInfo, VERR_CR_PKCS7_DIGEST_PARAMS_NOT_IMPL, "Digest algorithm has unsupported parameters"); + + RTCRDIGEST hDigest; + int rc = RTCrDigestCreateByObjId(&hDigest, &pSignerInfo->DigestAlgorithm.Algorithm); + if (RT_SUCCESS(rc)) + { + RTCrDigestRelease(*phDigest); + *phDigest = hDigest; + + /** @todo The encoding step modifies the data, contradicting the const-ness + * of the parameter. */ + rc = RTCrPkcs7Attributes_HashAttributes((PRTCRPKCS7ATTRIBUTES)&pSignerInfo->AuthenticatedAttributes, hDigest, pErrInfo); + } + return rc; +} + + +/** + * Find the handle to the digest given by the specified SignerInfo. + * + * @returns IPRT status code + * @param phDigest Where to return a referenced digest handle on + * success. + * @param pSignedData The signed data structure. + * @param pSignerInfo The signer info. + * @param pahDigests Array of content digests that runs parallel to + * pSignedData->DigestAlgorithms. + * @param pErrInfo Where to store additional error details, + * optional. + */ +static int rtCrPkcs7VerifyFindDigest(PRTCRDIGEST phDigest, PCRTCRPKCS7SIGNEDDATA pSignedData, + PCRTCRPKCS7SIGNERINFO pSignerInfo, PRTCRDIGEST pahDigests, PRTERRINFO pErrInfo) +{ + uint32_t iDigest = pSignedData->DigestAlgorithms.cItems; + while (iDigest-- > 0) + if (RTCrX509AlgorithmIdentifier_Compare(pSignedData->DigestAlgorithms.papItems[iDigest], + &pSignerInfo->DigestAlgorithm) == 0) + { + RTCRDIGEST hDigest = pahDigests[iDigest]; + uint32_t cRefs = RTCrDigestRetain(hDigest); + AssertReturn(cRefs != UINT32_MAX, VERR_CR_PKCS7_INTERNAL_ERROR); + *phDigest = hDigest; + return VINF_SUCCESS; + } + *phDigest = NIL_RTCRDIGEST; /* Make gcc happy. */ + return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_DIGEST_ALGO_NOT_FOUND_IN_LIST, + "SignerInfo.DigestAlgorithm %s not found.", + pSignerInfo->DigestAlgorithm.Algorithm.szObjId); +} + + +/** + * Verifies one signature on a PKCS \#7 SignedData. + * + * @returns IPRT status code. + * @param pSignerInfo The signature. + * @param pSignedData The SignedData. + * @param hDigests The digest corresponding to + * pSignerInfo->DigestAlgorithm. + * @param fFlags Verification flags. + * @param hAdditionalCerts Store containing optional certificates, + * optional. + * @param hTrustedCerts Store containing trusted certificates, required. + * @param pValidationTime The time we're supposed to validate the + * certificates chains at. + * @param pfnVerifyCert Signing certificate verification callback. + * @param fVccFlags Signing certificate verification callback flags. + * @param pvUser Callback parameter. + * @param pErrInfo Where to store additional error details, + * optional. + */ +static int rtCrPkcs7VerifySignerInfo(PCRTCRPKCS7SIGNERINFO pSignerInfo, PCRTCRPKCS7SIGNEDDATA pSignedData, + RTCRDIGEST hDigest, uint32_t fFlags, RTCRSTORE hAdditionalCerts, RTCRSTORE hTrustedCerts, + PCRTTIMESPEC pValidationTime, PFNRTCRPKCS7VERIFYCERTCALLBACK pfnVerifyCert, + uint32_t fVccFlags, void *pvUser, PRTERRINFO pErrInfo) +{ + /* + * Locate the certificate used for signing. + */ + PCRTCRCERTCTX pSignerCertCtx = NULL; + PCRTCRX509CERTIFICATE pSignerCert = NULL; + if (hTrustedCerts != NIL_RTCRSTORE) + pSignerCertCtx = RTCrStoreCertByIssuerAndSerialNo(hTrustedCerts, &pSignerInfo->IssuerAndSerialNumber.Name, + &pSignerInfo->IssuerAndSerialNumber.SerialNumber); + if (!pSignerCertCtx && hAdditionalCerts != NIL_RTCRSTORE) + pSignerCertCtx = RTCrStoreCertByIssuerAndSerialNo(hAdditionalCerts, &pSignerInfo->IssuerAndSerialNumber.Name, + &pSignerInfo->IssuerAndSerialNumber.SerialNumber); + if (pSignerCertCtx) + pSignerCert = pSignerCertCtx->pCert; + else + { + pSignerCert = RTCrPkcs7SetOfCerts_FindX509ByIssuerAndSerialNumber(&pSignedData->Certificates, + &pSignerInfo->IssuerAndSerialNumber.Name, + &pSignerInfo->IssuerAndSerialNumber.SerialNumber); + if (!pSignerCert) + return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_SIGNED_DATA_CERT_NOT_FOUND, + "Certificate not found: serial=%.*Rhxs", + pSignerInfo->IssuerAndSerialNumber.SerialNumber.Asn1Core.cb, + pSignerInfo->IssuerAndSerialNumber.SerialNumber.Asn1Core.uData.pv); + } + + /* + * Unless caller requesed all certificates to be trusted fully, we always + * pass it on to the certificate path builder so it can do the requested + * checks on trust anchors. (We didn't used to do this as the path + * builder could handle trusted targets. A benefit here is that + * pfnVerifyCert can assume a hCertPaths now, and get the validation time + * from it if it wants it.) + * + * If no valid paths are found, this step will fail. + */ + int rc; + if (!(fFlags & RTCRPKCS7VERIFY_SD_F_TRUST_ALL_CERTS)) + { + RTCRX509CERTPATHS hCertPaths; + rc = RTCrX509CertPathsCreate(&hCertPaths, pSignerCert); + if (RT_SUCCESS(rc)) + { + rc = RTCrX509CertPathsSetValidTimeSpec(hCertPaths, pValidationTime); + if (hTrustedCerts != NIL_RTCRSTORE && RT_SUCCESS(rc)) + rc = RTCrX509CertPathsSetTrustedStore(hCertPaths, hTrustedCerts); + if (hAdditionalCerts != NIL_RTCRSTORE && RT_SUCCESS(rc)) + rc = RTCrX509CertPathsSetUntrustedStore(hCertPaths, hAdditionalCerts); + if (pSignedData->Certificates.cItems > 0 && RT_SUCCESS(rc)) + rc = RTCrX509CertPathsSetUntrustedSet(hCertPaths, &pSignedData->Certificates); + if ((fFlags & RTCRPKCS7VERIFY_SD_F_CHECK_TRUST_ANCHORS) && RT_SUCCESS(rc)) + rc = RTCrX509CertPathsSetTrustAnchorChecks(hCertPaths, true /*fEnable*/); + if (RT_SUCCESS(rc)) + { + rc = RTCrX509CertPathsBuild(hCertPaths, pErrInfo); + if (RT_SUCCESS(rc)) + rc = RTCrX509CertPathsValidateAll(hCertPaths, NULL, pErrInfo); + + /* + * Check that the certificate purpose and whatnot matches what + * is being signed. + */ + if (RT_SUCCESS(rc)) + rc = pfnVerifyCert(pSignerCert, hCertPaths, fVccFlags, pvUser, pErrInfo); + } + else + RTErrInfoSetF(pErrInfo, rc, "Error configuring path builder: %Rrc", rc); + RTCrX509CertPathsRelease(hCertPaths); + } + } + /* + * Check that the certificate purpose matches what is signed. + */ + else + rc = pfnVerifyCert(pSignerCert, NIL_RTCRX509CERTPATHS, fVccFlags, pvUser, pErrInfo); + + /* + * Reference the digest so we can safely replace with one on the + * authenticated attributes below. + */ + if ( RT_SUCCESS(rc) + && RTCrDigestRetain(hDigest) != UINT32_MAX) + { + /* + * If there are authenticated attributes, we've got more work before we + * can verify the signature. + */ + if ( RT_SUCCESS(rc) + && RTCrPkcs7Attributes_IsPresent(&pSignerInfo->AuthenticatedAttributes)) + rc = rtCrPkcs7VerifySignerInfoAuthAttribs(pSignerInfo, pSignedData, &hDigest, fFlags, pErrInfo); + + /* + * Verify the signature. + */ + if (RT_SUCCESS(rc)) + { + RTCRKEY hKey; + rc = RTCrKeyCreateFromSubjectPublicKeyInfo(&hKey, &pSignerCert->TbsCertificate.SubjectPublicKeyInfo, + pErrInfo, "pkcs7"); + if (RT_SUCCESS(rc)) + { + RTCRPKIXSIGNATURE hSignature; + rc = RTCrPkixSignatureCreateByObjId(&hSignature, &pSignerInfo->DigestEncryptionAlgorithm.Algorithm, + hKey, &pSignerInfo->DigestEncryptionAlgorithm.Parameters, false /*fSigning*/); + RTCrKeyRelease(hKey); + if (RT_SUCCESS(rc)) + { + /** @todo Check that DigestEncryptionAlgorithm is compatible with hSignature + * (this is not vital). */ + rc = RTCrPkixSignatureVerifyOctetString(hSignature, hDigest, &pSignerInfo->EncryptedDigest); + if (RT_FAILURE(rc)) + rc = RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_SIGNATURE_VERIFICATION_FAILED, + "Signature verification failed: %Rrc", rc); + RTCrPkixSignatureRelease(hSignature); + } + else + rc = RTErrInfoSetF(pErrInfo, rc, "Failure to instantiate public key algorithm [IPRT]: %s (%s)", + pSignerCert->TbsCertificate.SubjectPublicKeyInfo.Algorithm.Algorithm.szObjId, + pSignerInfo->DigestEncryptionAlgorithm.Algorithm.szObjId); + } + } + + RTCrDigestRelease(hDigest); + } + else if (RT_SUCCESS(rc)) + rc = VERR_CR_PKCS7_INTERNAL_ERROR; + RTCrCertCtxRelease(pSignerCertCtx); + return rc; +} + + +/** + * Verifies a counter signature. + * + * @returns IPRT status code. + * @param pCounterSignerInfo The counter signature. + * @param pPrimarySignerInfo The primary signature (can be a counter + * signature too if nested). + * @param pSignedData The SignedData. + * @param fFlags Verification flags. + * @param hAdditionalCerts Store containing optional certificates, + * optional. + * @param hTrustedCerts Store containing trusted certificates, required. + * @param pValidationTime The time we're supposed to validate the + * certificates chains at. + * @param pfnVerifyCert Signing certificate verification callback. + * @param fVccFlags Signing certificate verification callback flags. + * @param pvUser Callback parameter. + * @param pErrInfo Where to store additional error details, + * optional. + */ +static int rtCrPkcs7VerifyCounterSignerInfo(PCRTCRPKCS7SIGNERINFO pCounterSignerInfo, PCRTCRPKCS7SIGNERINFO pPrimarySignerInfo, + PCRTCRPKCS7SIGNEDDATA pSignedData, uint32_t fFlags, + RTCRSTORE hAdditionalCerts, RTCRSTORE hTrustedCerts, PCRTTIMESPEC pValidationTime, + PFNRTCRPKCS7VERIFYCERTCALLBACK pfnVerifyCert, uint32_t fVccFlags, + void *pvUser, PRTERRINFO pErrInfo) +{ + /* + * Calculate the digest we need to verify. + */ + RTCRDIGEST hDigest; + int rc = RTCrDigestCreateByObjId(&hDigest, &pCounterSignerInfo->DigestAlgorithm.Algorithm); + if (RT_SUCCESS(rc)) + { + rc = RTCrDigestUpdate(hDigest, + pPrimarySignerInfo->EncryptedDigest.Asn1Core.uData.pv, + pPrimarySignerInfo->EncryptedDigest.Asn1Core.cb); + if (RT_SUCCESS(rc)) + rc = RTCrDigestFinal(hDigest, NULL, 0); + if (RT_SUCCESS(rc)) + { + /* + * Pass it on to the common SignerInfo verifier function. + */ + rc = rtCrPkcs7VerifySignerInfo(pCounterSignerInfo, pSignedData, hDigest, + fFlags | RTCRPKCS7VERIFY_SD_F_COUNTER_SIGNATURE, + hAdditionalCerts, hTrustedCerts, pValidationTime, + pfnVerifyCert, fVccFlags, pvUser, pErrInfo); + + } + else + rc = RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_DIGEST_CALC_ERROR, + "Hashing for counter signature failed unexpectedly: %Rrc", rc); + RTCrDigestRelease(hDigest); + } + else + rc = RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_DIGEST_CREATE_ERROR, "Error creating digest for '%s': %Rrc", + pCounterSignerInfo->DigestAlgorithm.Algorithm.szObjId, rc); + + return rc; +} + + +/** + * Worker. + */ +static int rtCrPkcs7VerifySignedDataEx(PCRTCRPKCS7CONTENTINFO pContentInfo, uint32_t fFlags, + RTCRSTORE hAdditionalCerts, RTCRSTORE hTrustedCerts, + PCRTTIMESPEC pValidationTime, + PFNRTCRPKCS7VERIFYCERTCALLBACK pfnVerifyCert, void *pvUser, + void const *pvContent, size_t cbContent, PRTERRINFO pErrInfo) +{ + /* + * Check and adjust the input. + */ + if (pfnVerifyCert) + AssertPtrReturn(pfnVerifyCert, VERR_INVALID_POINTER); + else + pfnVerifyCert = RTCrPkcs7VerifyCertCallbackDefault; + + if (!RTCrPkcs7ContentInfo_IsSignedData(pContentInfo)) + return RTErrInfoSet(pErrInfo, VERR_CR_PKCS7_NOT_SIGNED_DATA, "Not PKCS #7 SignedData."); + PCRTCRPKCS7SIGNEDDATA pSignedData = pContentInfo->u.pSignedData; + int rc = RTCrPkcs7SignedData_CheckSanity(pSignedData, 0, pErrInfo, ""); + if (RT_FAILURE(rc)) + return rc; + + /* + * Hash the content info. + */ + /* Check that there aren't too many or too few hash algorithms for our + implementation and purposes. */ + RTCRDIGEST ahDigests[2]; + uint32_t const cDigests = pSignedData->DigestAlgorithms.cItems; + if (!cDigests) /** @todo we might have to support this... */ + return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_NO_DIGEST_ALGORITHMS, "No digest algorithms"); + + if (cDigests > RT_ELEMENTS(ahDigests)) + return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_TOO_MANY_DIGEST_ALGORITHMS, + "Too many digest algorithm: cAlgorithms=%u", cDigests); + + /* Create the message digest calculators. */ + rc = VERR_CR_PKCS7_NO_DIGEST_ALGORITHMS; + uint32_t i; + for (i = 0; i < cDigests; i++) + { + rc = RTCrDigestCreateByObjId(&ahDigests[i], &pSignedData->DigestAlgorithms.papItems[i]->Algorithm); + if (RT_FAILURE(rc)) + { + rc = RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_DIGEST_CREATE_ERROR, "Error creating digest for '%s': %Rrc", + pSignedData->DigestAlgorithms.papItems[i]->Algorithm.szObjId, rc); + break; + } + } + if (RT_SUCCESS(rc)) + { + /* Hash the content. */ + for (i = 0; i < cDigests && RT_SUCCESS(rc); i++) + { + rc = RTCrDigestUpdate(ahDigests[i], pvContent, cbContent); + if (RT_SUCCESS(rc)) + rc = RTCrDigestFinal(ahDigests[i], NULL, 0); + } + if (RT_SUCCESS(rc)) + { + /* + * Validate the signed infos. The flags may select one particular entry. + */ + RTTIMESPEC const GivenValidationTime = *pValidationTime; + uint32_t fPrimaryVccFlags = !(fFlags & RTCRPKCS7VERIFY_SD_F_USAGE_TIMESTAMPING) + ? RTCRPKCS7VCC_F_SIGNED_DATA : RTCRPKCS7VCC_F_TIMESTAMP; + uint32_t cItems = pSignedData->SignerInfos.cItems; + i = 0; + if (fFlags & RTCRPKCS7VERIFY_SD_F_HAS_SIGNER_INDEX) + { + i = (fFlags & RTCRPKCS7VERIFY_SD_F_SIGNER_INDEX_MASK) >> RTCRPKCS7VERIFY_SD_F_SIGNER_INDEX_SHIFT; + cItems = RT_MIN(cItems, i + 1); + } + rc = VERR_CR_PKCS7_NO_SIGNER_INFOS; + for (; i < cItems; i++) + { + PCRTCRPKCS7SIGNERINFO pSignerInfo = pSignedData->SignerInfos.papItems[i]; + RTCRDIGEST hThisDigest = NIL_RTCRDIGEST; /* (gcc maybe incredible stupid.) */ + rc = rtCrPkcs7VerifyFindDigest(&hThisDigest, pSignedData, pSignerInfo, ahDigests, pErrInfo); + if (RT_FAILURE(rc)) + break; + + /* + * See if we can find a trusted signing time. + * (Note that while it would make sense splitting up this function, + * we need to carry a lot of arguments around, so better not.) + */ + bool fDone = false; + PCRTCRPKCS7SIGNERINFO pSigningTimeSigner = NULL; + PCRTASN1TIME pSignedTime; + while ( !fDone + && (pSignedTime = RTCrPkcs7SignerInfo_GetSigningTime(pSignerInfo, &pSigningTimeSigner)) != NULL) + { + RTTIMESPEC ThisValidationTime; + if (RT_LIKELY(RTTimeImplode(&ThisValidationTime, &pSignedTime->Time))) + { + if (pSigningTimeSigner == pSignerInfo) + { + if (fFlags & RTCRPKCS7VERIFY_SD_F_COUNTER_SIGNATURE_SIGNING_TIME_ONLY) + continue; + rc = rtCrPkcs7VerifySignerInfo(pSignerInfo, pSignedData, hThisDigest, fFlags, + hAdditionalCerts, hTrustedCerts, &ThisValidationTime, + pfnVerifyCert, fPrimaryVccFlags | RTCRPKCS7VCC_F_TIMESTAMP, + pvUser, pErrInfo); + } + else + { + rc = VINF_SUCCESS; + if (!(fFlags & RTCRPKCS7VERIFY_SD_F_USE_SIGNING_TIME_UNVERIFIED)) + rc = rtCrPkcs7VerifyCounterSignerInfo(pSigningTimeSigner, pSignerInfo, pSignedData, + fFlags & ~RTCRPKCS7VERIFY_SD_F_UPDATE_VALIDATION_TIME, + hAdditionalCerts, hTrustedCerts, &ThisValidationTime, + pfnVerifyCert, RTCRPKCS7VCC_F_TIMESTAMP, pvUser, pErrInfo); + if (RT_SUCCESS(rc)) + rc = rtCrPkcs7VerifySignerInfo(pSignerInfo, pSignedData, hThisDigest, fFlags, hAdditionalCerts, + hTrustedCerts, &ThisValidationTime, + pfnVerifyCert, fPrimaryVccFlags, pvUser, pErrInfo); + } + fDone = RT_SUCCESS(rc) + || (fFlags & RTCRPKCS7VERIFY_SD_F_ALWAYS_USE_SIGNING_TIME_IF_PRESENT); + if ((fFlags & RTCRPKCS7VERIFY_SD_F_UPDATE_VALIDATION_TIME) && fDone) + *(PRTTIMESPEC)pValidationTime = ThisValidationTime; + } + else + { + rc = RTErrInfoSet(pErrInfo, VERR_INTERNAL_ERROR_3, "RTTimeImplode failed"); + fDone = true; + } + } + + /* + * If not luck, check for microsoft timestamp counter signatures. + */ + if (!fDone && !(fFlags & RTCRPKCS7VERIFY_SD_F_IGNORE_MS_TIMESTAMP)) + { + PCRTCRPKCS7CONTENTINFO pSignedTimestamp = NULL; + pSignedTime = RTCrPkcs7SignerInfo_GetMsTimestamp(pSignerInfo, &pSignedTimestamp); + if (pSignedTime) + { + RTTIMESPEC ThisValidationTime; + if (RT_LIKELY(RTTimeImplode(&ThisValidationTime, &pSignedTime->Time))) + { + rc = VINF_SUCCESS; + if (!(fFlags & RTCRPKCS7VERIFY_SD_F_USE_MS_TIMESTAMP_UNVERIFIED)) + rc = RTCrPkcs7VerifySignedData(pSignedTimestamp, + fFlags | RTCRPKCS7VERIFY_SD_F_IGNORE_MS_TIMESTAMP + | RTCRPKCS7VERIFY_SD_F_USAGE_TIMESTAMPING, + hAdditionalCerts, hTrustedCerts, &ThisValidationTime, + pfnVerifyCert, pvUser, pErrInfo); + + if (RT_SUCCESS(rc)) + rc = rtCrPkcs7VerifySignerInfo(pSignerInfo, pSignedData, hThisDigest, fFlags, hAdditionalCerts, + hTrustedCerts, &ThisValidationTime, + pfnVerifyCert, fPrimaryVccFlags, pvUser, pErrInfo); + fDone = RT_SUCCESS(rc) + || (fFlags & RTCRPKCS7VERIFY_SD_F_ALWAYS_USE_MS_TIMESTAMP_IF_PRESENT); + if ((fFlags & RTCRPKCS7VERIFY_SD_F_UPDATE_VALIDATION_TIME) && fDone) + *(PRTTIMESPEC)pValidationTime = ThisValidationTime; + } + else + { + rc = RTErrInfoSet(pErrInfo, VERR_INTERNAL_ERROR_3, "RTTimeImplode failed"); + fDone = true; + } + + } + } + + /* + * No valid signing time found, use the one specified instead. + */ + if (!fDone) + rc = rtCrPkcs7VerifySignerInfo(pSignerInfo, pSignedData, hThisDigest, fFlags, hAdditionalCerts, hTrustedCerts, + &GivenValidationTime, pfnVerifyCert, fPrimaryVccFlags, pvUser, pErrInfo); + RTCrDigestRelease(hThisDigest); + if (RT_FAILURE(rc)) + break; + } + } + else + rc = RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_DIGEST_CALC_ERROR, + "Hashing content failed unexpectedly (i=%u): %Rrc", i, rc); + + /* Clean up digests. */ + i = cDigests; + } + while (i-- > 0) + { + int rc2 = RTCrDigestRelease(ahDigests[i]); + AssertRC(rc2); + } + + +#ifdef IPRT_WITH_OPENSSL + /* + * Verify using OpenSSL and combine the results (should be identical). + */ + /** @todo figure out how to verify MS timstamp signatures using OpenSSL. */ + if (fFlags & RTCRPKCS7VERIFY_SD_F_USAGE_TIMESTAMPING) + return rc; + /** @todo figure out if we can verify just one signer info item using OpenSSL. */ + if (!(fFlags & RTCRPKCS7VERIFY_SD_F_HAS_SIGNER_INDEX) && pSignedData->SignerInfos.cItems > 1) + return rc; + + int rcOssl = rtCrPkcs7VerifySignedDataUsingOpenSsl(pContentInfo, fFlags, hAdditionalCerts, hTrustedCerts, + pvContent, cbContent, RT_SUCCESS(rc) ? pErrInfo : NULL); + if (RT_SUCCESS(rcOssl) && RT_SUCCESS(rc)) + return rc; +// AssertMsg(RT_FAILURE_NP(rcOssl) && RT_FAILURE_NP(rc), ("%Rrc, %Rrc\n", rcOssl, rc)); + if (RT_FAILURE(rc)) + return rc; + return rcOssl; +#else + return rc; +#endif +} + + +RTDECL(int) RTCrPkcs7VerifySignedData(PCRTCRPKCS7CONTENTINFO pContentInfo, uint32_t fFlags, + RTCRSTORE hAdditionalCerts, RTCRSTORE hTrustedCerts, + PCRTTIMESPEC pValidationTime, PFNRTCRPKCS7VERIFYCERTCALLBACK pfnVerifyCert, void *pvUser, + PRTERRINFO pErrInfo) +{ + /* + * Find the content and pass it on to common worker. + */ + if (!RTCrPkcs7ContentInfo_IsSignedData(pContentInfo)) + return RTErrInfoSet(pErrInfo, VERR_CR_PKCS7_NOT_SIGNED_DATA, "Not PKCS #7 SignedData."); + + /* Exactly what the content is, is for some stupid reason unnecessarily complicated. */ + PCRTCRPKCS7SIGNEDDATA pSignedData = pContentInfo->u.pSignedData; + void const *pvContent = pSignedData->ContentInfo.Content.Asn1Core.uData.pv; + uint32_t cbContent = pSignedData->ContentInfo.Content.Asn1Core.cb; + if (pSignedData->ContentInfo.Content.pEncapsulated) + { + pvContent = pSignedData->ContentInfo.Content.pEncapsulated->uData.pv; + cbContent = pSignedData->ContentInfo.Content.pEncapsulated->cb; + } + + return rtCrPkcs7VerifySignedDataEx(pContentInfo, fFlags, hAdditionalCerts, hTrustedCerts, pValidationTime, + pfnVerifyCert, pvUser, pvContent, cbContent, pErrInfo); +} + + +RTDECL(int) RTCrPkcs7VerifySignedDataWithExternalData(PCRTCRPKCS7CONTENTINFO pContentInfo, uint32_t fFlags, + RTCRSTORE hAdditionalCerts, RTCRSTORE hTrustedCerts, + PCRTTIMESPEC pValidationTime, + PFNRTCRPKCS7VERIFYCERTCALLBACK pfnVerifyCert, void *pvUser, + void const *pvData, size_t cbData, PRTERRINFO pErrInfo) +{ + /* + * Require 'data' as inner content type. + */ + if (!RTCrPkcs7ContentInfo_IsSignedData(pContentInfo)) + return RTErrInfoSet(pErrInfo, VERR_CR_PKCS7_NOT_SIGNED_DATA, "Not PKCS #7 SignedData."); + PCRTCRPKCS7SIGNEDDATA pSignedData = pContentInfo->u.pSignedData; + + if (RTAsn1ObjId_CompareWithString(&pSignedData->ContentInfo.ContentType, RTCR_PKCS7_DATA_OID) != 0) + return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_NOT_DATA, + "The signedData content type is %s, expected 'data' (%s)", + pSignedData->ContentInfo.ContentType.szObjId, RTCR_PKCS7_DATA_OID); + + return rtCrPkcs7VerifySignedDataEx(pContentInfo, fFlags, hAdditionalCerts, hTrustedCerts, pValidationTime, + pfnVerifyCert, pvUser, pvData, cbData, pErrInfo); +} + diff --git a/src/VBox/Runtime/common/crypto/pkix-sign.cpp b/src/VBox/Runtime/common/crypto/pkix-sign.cpp new file mode 100644 index 00000000..efe9d3f3 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/pkix-sign.cpp @@ -0,0 +1,266 @@ +/* $Id: pkix-sign.cpp $ */ +/** @file + * IPRT - Crypto - Public Key Infrastructure API, Verification. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/pkix.h> + +#include <iprt/alloca.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/crypto/digest.h> +#include <iprt/crypto/key.h> + +#ifdef IPRT_WITH_OPENSSL +# include "internal/iprt-openssl.h" +# include "internal/openssl-pre.h" +# include <openssl/evp.h> +# include <openssl/rsa.h> +# include "internal/openssl-post.h" +# ifndef OPENSSL_VERSION_NUMBER +# error "Missing OPENSSL_VERSION_NUMBER!" +# endif +#endif + + +#if 0 +RTDECL(int) RTCrPkixPubKeySignData(PCRTASN1OBJID pAlgorithm, PCRTASN1DYNTYPE pParameters, PCRTASN1BITSTRING pPrivateKey, + PCRTASN1BITSTRING pSignatureValue, const void *pvData, size_t cbData, PRTERRINFO pErrInfo) +{ + /* + * Validate the digest related inputs. + */ + AssertPtrReturn(pAlgorithm, VERR_INVALID_POINTER); + AssertReturn(RTAsn1ObjId_IsPresent(pAlgorithm), VERR_INVALID_POINTER); + + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + AssertReturn(cbData > 0, VERR_INVALID_PARAMETER); + + /* + * Digest the data and call the other API. + */ + RTCRDIGEST hDigest; + int rc = RTCrDigestCreateByObjId(&hDigest, pAlgorithm); + if (RT_SUCCESS(rcIprt)) + { + rc = RTCrDigestUpdate(hDigest, pvData, cbData); + if (RT_SUCCESS(rcIprt)) + rc = RTCrPkixPubKeySignDigest(pAlgorithm, pParameters, pPrivateKey, pvSignedDigest, cbSignedDigest, hDigest, pErrInfo); + else + RTErrInfoSet(pErrInfo, rcIprt, "RTCrDigestUpdate failed"); + RTCrDigestRelease(hDigest); + } + else + RTErrInfoSetF(pErrInfo, rcIprt, "Unknown digest algorithm [IPRT]: %s", pAlgorithm->szObjId); + return rc; +} +#endif + + +RTDECL(int) RTCrPkixPubKeySignDigest(PCRTASN1OBJID pAlgorithm, RTCRKEY hPrivateKey, PCRTASN1DYNTYPE pParameters, + RTCRDIGEST hDigest, uint32_t fFlags, + void *pvSignature, size_t *pcbSignature, PRTERRINFO pErrInfo) +{ + /* + * Valid input. + */ + AssertPtrReturn(pAlgorithm, VERR_INVALID_POINTER); + AssertReturn(RTAsn1ObjId_IsPresent(pAlgorithm), VERR_INVALID_POINTER); + + if (pParameters) + { + AssertPtrReturn(pParameters, VERR_INVALID_POINTER); + if (pParameters->enmType == RTASN1TYPE_NULL) + pParameters = NULL; + } + + AssertPtrReturn(hPrivateKey, VERR_INVALID_POINTER); + Assert(RTCrKeyHasPrivatePart(hPrivateKey)); + + AssertPtrReturn(pcbSignature, VERR_INVALID_PARAMETER); + size_t cbSignature = *pcbSignature; + if (cbSignature) + AssertPtrReturn(pvSignature, VERR_INVALID_POINTER); + else + pvSignature = NULL; + + AssertPtrReturn(hDigest, VERR_INVALID_HANDLE); + + AssertReturn(fFlags == 0, VERR_INVALID_FLAGS); + + /* + * Parameters are not currently supported (openssl code path). + */ + if (pParameters) + return RTErrInfoSet(pErrInfo, VERR_CR_PKIX_CIPHER_ALGO_PARAMS_NOT_IMPL, + "Cipher algorithm parameters are not yet supported."); + + /* + * Sign using IPRT. + */ + RTCRPKIXSIGNATURE hSignature; + int rcIprt = RTCrPkixSignatureCreateByObjId(&hSignature, pAlgorithm, hPrivateKey, pParameters, true /*fSigning*/); + if (RT_FAILURE(rcIprt)) + return RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_CIPHER_ALGO_NOT_KNOWN, + "Unknown private key algorithm [IPRT]: %s", pAlgorithm->szObjId); + + rcIprt = RTCrPkixSignatureSign(hSignature, hDigest, pvSignature, pcbSignature); + if (RT_FAILURE(rcIprt)) + RTErrInfoSet(pErrInfo, rcIprt, "RTCrPkixSignatureSign failed"); + + RTCrPkixSignatureRelease(hSignature); + + /* + * Sign using OpenSSL EVP if we can. + */ +#if defined(IPRT_WITH_OPENSSL) \ + && (OPENSSL_VERSION_NUMBER > 0x10000000L) /* 0.9.8 doesn't seem to have EVP_PKEY_CTX_set_signature_md. */ + + /* Make sure the algorithm includes the digest and isn't just RSA. */ + const char *pszAlgObjId = pAlgorithm->szObjId; + if (!strcmp(pszAlgObjId, RTCRX509ALGORITHMIDENTIFIERID_RSA)) + { + pszAlgObjId = RTCrX509AlgorithmIdentifier_CombineEncryptionOidAndDigestOid(pszAlgObjId, + RTCrDigestGetAlgorithmOid(hDigest)); + AssertMsgStmt(pszAlgObjId, ("enc=%s hash=%s\n", pAlgorithm->szObjId, RTCrDigestGetAlgorithmOid(hDigest)), + pszAlgObjId = RTCrDigestGetAlgorithmOid(hDigest)); + } + + /* Create an EVP private key. */ + EVP_PKEY *pEvpPrivateKey = NULL; + const EVP_MD *pEvpMdType = NULL; + int rcOssl = rtCrKeyToOpenSslKeyEx(hPrivateKey, false /*fNeedPublic*/, pszAlgObjId, + (void **)&pEvpPrivateKey, (const void **)&pEvpMdType, pErrInfo); + if (RT_SUCCESS(rcOssl)) + { + /* Create an EVP Private key context we can use to validate the digest. */ + EVP_PKEY_CTX *pEvpPKeyCtx = EVP_PKEY_CTX_new(pEvpPrivateKey, NULL); + if (pEvpPKeyCtx) + { + rcOssl = EVP_PKEY_sign_init(pEvpPKeyCtx); + if (rcOssl > 0) + { + rcOssl = EVP_PKEY_CTX_set_rsa_padding(pEvpPKeyCtx, RSA_PKCS1_PADDING); + if (rcOssl > 0) + { + rcOssl = EVP_PKEY_CTX_set_signature_md(pEvpPKeyCtx, pEvpMdType); + if (rcOssl > 0) + { + /* Allocate a signature buffer. */ + unsigned char *pbOsslSignature = NULL; + void *pvOsslSignatureFree = NULL; + size_t cbOsslSignature = cbSignature; + if (cbOsslSignature > 0) + { + if (cbOsslSignature < _1K) + pbOsslSignature = (unsigned char *)alloca(cbOsslSignature); + else + { + pbOsslSignature = (unsigned char *)RTMemTmpAlloc(cbOsslSignature); + pvOsslSignatureFree = pbOsslSignature; + } + } + if (cbOsslSignature == 0 || pbOsslSignature != NULL) + { + /* Get the digest from hDigest and sign it. */ + rcOssl = EVP_PKEY_sign(pEvpPKeyCtx, + pbOsslSignature, + &cbOsslSignature, + (const unsigned char *)RTCrDigestGetHash(hDigest), + RTCrDigestGetHashSize(hDigest)); + if (rcOssl > 0) + { + /* Compare the result. The memcmp assums no random padding bits. */ + rcOssl = VINF_SUCCESS; + AssertMsgStmt(cbOsslSignature == *pcbSignature, + ("cbOsslSignature=%#x, iprt %#x\n", cbOsslSignature, *pcbSignature), + rcOssl = VERR_CR_PKIX_OSSL_VS_IPRT_SIGNATURE_SIZE); + AssertMsgStmt( pbOsslSignature == NULL + || rcOssl != VINF_SUCCESS + || memcmp(pbOsslSignature, pvSignature, cbOsslSignature) == 0, + ("OpenSSL: %.*Rhxs\n" + "IPRT: %.*Rhxs\n", + cbOsslSignature, pbOsslSignature, *pcbSignature, pvSignature), + rcOssl = VERR_CR_PKIX_OSSL_VS_IPRT_SIGNATURE); + if (!pbOsslSignature && rcOssl == VINF_SUCCESS) + rcOssl = VERR_BUFFER_OVERFLOW; + } + else + rcOssl = RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_OSSL_SIGN_FINAL_FAILED, + "EVP_PKEY_sign failed (%d)", rcOssl); + if (pvOsslSignatureFree) + RTMemTmpFree(pvOsslSignatureFree); + } + else + rcOssl = VERR_NO_TMP_MEMORY; + } + else + rcOssl = RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_OSSL_EVP_PKEY_TYPE_ERROR, + "EVP_PKEY_CTX_set_signature_md failed (%d)", rcOssl); + } + else + rcOssl = RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_OSSL_EVP_PKEY_RSA_PAD_ERROR, + "EVP_PKEY_CTX_set_rsa_padding failed (%d)", rcOssl); + } + else + rcOssl = RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_OSSL_EVP_PKEY_TYPE_ERROR, + "EVP_PKEY_verify_init failed (%d)", rcOssl); + EVP_PKEY_CTX_free(pEvpPKeyCtx); + } + else + rcOssl = RTErrInfoSet(pErrInfo, VERR_CR_PKIX_OSSL_EVP_PKEY_TYPE_ERROR, "EVP_PKEY_CTX_new failed"); + EVP_PKEY_free(pEvpPrivateKey); + } + + /* + * Check the result. + */ + if ( (RT_SUCCESS(rcIprt) && RT_SUCCESS(rcOssl)) + || (RT_FAILURE_NP(rcIprt) && RT_FAILURE_NP(rcOssl)) + || (RT_SUCCESS(rcIprt) && rcOssl == VERR_CR_PKIX_OSSL_CIPHER_ALGO_NOT_KNOWN_EVP) ) + return rcIprt; + AssertMsgFailed(("rcIprt=%Rrc rcOssl=%Rrc\n", rcIprt, rcOssl)); + if (RT_FAILURE_NP(rcOssl)) + return rcOssl; +#endif /* IPRT_WITH_OPENSSL */ + + return rcIprt; +} + diff --git a/src/VBox/Runtime/common/crypto/pkix-signature-builtin.cpp b/src/VBox/Runtime/common/crypto/pkix-signature-builtin.cpp new file mode 100644 index 00000000..6a496c66 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/pkix-signature-builtin.cpp @@ -0,0 +1,150 @@ +/* $Id: pkix-signature-builtin.cpp $ */ +/** @file + * IPRT - Crypto - Public Key Signature Schemas, Built-in providers. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/pkix.h> + +#include <iprt/errcore.h> +#include <iprt/string.h> + +#ifdef IPRT_WITH_OPENSSL +# include "internal/iprt-openssl.h" +# include "internal/openssl-pre.h" +# include <openssl/evp.h> +# include "internal/openssl-post.h" +#endif + +#include "pkix-signature-builtin.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** + * Array of built in message digest vtables. + */ +static PCRTCRPKIXSIGNATUREDESC const g_apPkixSignatureDescriptors[] = +{ + &g_rtCrPkixSigningHashWithRsaDesc, +}; + + + +PCRTCRPKIXSIGNATUREDESC RTCrPkixSignatureFindByObjIdString(const char *pszObjId, void **ppvOpaque) +{ + if (ppvOpaque) + *ppvOpaque = NULL; + + /* + * Primary OIDs. + */ + uint32_t i = RT_ELEMENTS(g_apPkixSignatureDescriptors); + while (i-- > 0) + if (strcmp(g_apPkixSignatureDescriptors[i]->pszObjId, pszObjId) == 0) + return g_apPkixSignatureDescriptors[i]; + + /* + * Alias OIDs. + */ + i = RT_ELEMENTS(g_apPkixSignatureDescriptors); + while (i-- > 0) + { + const char * const *ppszAliases = g_apPkixSignatureDescriptors[i]->papszObjIdAliases; + if (ppszAliases) + for (; *ppszAliases; ppszAliases++) + if (strcmp(*ppszAliases, pszObjId) == 0) + return g_apPkixSignatureDescriptors[i]; + } + +#if 0//def IPRT_WITH_OPENSSL + /* + * Try EVP and see if it knows the algorithm. + */ + if (ppvOpaque) + { + rtCrOpenSslInit(); + int iAlgoNid = OBJ_txt2nid(pszObjId); + if (iAlgoNid != NID_undef) + { + const char *pszAlogSn = OBJ_nid2sn(iAlgoNid); + const EVP_MD *pEvpMdType = EVP_get_digestbyname(pszAlogSn); + if (pEvpMdType) + { + /* + * Return the OpenSSL provider descriptor and the EVP_MD address. + */ + Assert(pEvpMdType->md_size); + *ppvOpaque = (void *)pEvpMdType; + return &g_rtCrPkixSignatureOpenSslDesc; + } + } + } +#endif + return NULL; +} + + +PCRTCRPKIXSIGNATUREDESC RTCrPkixSignatureFindByObjId(PCRTASN1OBJID pObjId, void **ppvOpaque) +{ + return RTCrPkixSignatureFindByObjIdString(pObjId->szObjId, ppvOpaque); +} + + +RTDECL(int) RTCrPkixSignatureCreateByObjIdString(PRTCRPKIXSIGNATURE phSignature, const char *pszObjId, + RTCRKEY hKey, PCRTASN1DYNTYPE pParams, bool fSigning) +{ + void *pvOpaque; + PCRTCRPKIXSIGNATUREDESC pDesc = RTCrPkixSignatureFindByObjIdString(pszObjId, &pvOpaque); + if (pDesc) + return RTCrPkixSignatureCreate(phSignature, pDesc, pvOpaque, fSigning, hKey, pParams); + return VERR_NOT_FOUND; +} + + +RTDECL(int) RTCrPkixSignatureCreateByObjId(PRTCRPKIXSIGNATURE phSignature, PCRTASN1OBJID pObjId, + RTCRKEY hKey, PCRTASN1DYNTYPE pParams, bool fSigning) +{ + void *pvOpaque; + PCRTCRPKIXSIGNATUREDESC pDesc = RTCrPkixSignatureFindByObjId(pObjId, &pvOpaque); + if (pDesc) + return RTCrPkixSignatureCreate(phSignature, pDesc, pvOpaque, fSigning, hKey, pParams); + return VERR_NOT_FOUND; +} + diff --git a/src/VBox/Runtime/common/crypto/pkix-signature-builtin.h b/src/VBox/Runtime/common/crypto/pkix-signature-builtin.h new file mode 100644 index 00000000..f2c7da18 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/pkix-signature-builtin.h @@ -0,0 +1,48 @@ +/* $Id: pkix-signature-builtin.h $ */ +/** @file + * IPRT - Crypto - Public Key Signature Schemas, Built-in providers. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef IPRT_INCLUDED_SRC_common_crypto_pkix_signature_builtin_h +#define IPRT_INCLUDED_SRC_common_crypto_pkix_signature_builtin_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/crypto/pkix.h> + +extern DECL_HIDDEN_DATA(RTCRPKIXSIGNATUREDESC const) g_rtCrPkixSigningHashWithRsaDesc; + +#endif /* !IPRT_INCLUDED_SRC_common_crypto_pkix_signature_builtin_h */ + diff --git a/src/VBox/Runtime/common/crypto/pkix-signature-core.cpp b/src/VBox/Runtime/common/crypto/pkix-signature-core.cpp new file mode 100644 index 00000000..8378a362 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/pkix-signature-core.cpp @@ -0,0 +1,298 @@ +/* $Id: pkix-signature-core.cpp $ */ +/** @file + * IPRT - Crypto - Public Key Signature Schema Algorithm, Core API. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/pkix.h> + +#include <iprt/assert.h> +#include <iprt/asm.h> +#include <iprt/errcore.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/crypto/digest.h> +#include <iprt/crypto/key.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Generic public key signature scheme instance. + */ +typedef struct RTCRPKIXSIGNATUREINT +{ + /** Magic value (RTCRPKIXSIGNATUREINT_MAGIC). */ + uint32_t u32Magic; + /** Reference counter. */ + uint32_t volatile cRefs; + /** Pointer to the message digest descriptor. */ + PCRTCRPKIXSIGNATUREDESC pDesc; + /** Key being used (referrenced of course). */ + RTCRKEY hKey; + /** The operation this instance is licensed for. */ + bool fSigning; + /** State. */ + uint32_t uState; + + /** Opaque data specific to the message digest algorithm, size given by + * RTCRPKIXSIGNATUREDESC::cbState. */ + uint8_t abState[1]; +} RTCRPKIXSIGNATUREINT; +AssertCompileMemberAlignment(RTCRPKIXSIGNATUREINT, abState, 8); +/** Pointer to a public key algorithm instance. */ +typedef RTCRPKIXSIGNATUREINT *PRTCRPKIXSIGNATUREINT; + +/** Magic value for RTCRPKIXSIGNATUREINT::u32Magic (Baley Withfield Diffie). */ +#define RTCRPKIXSIGNATUREINT_MAGIC UINT32_C(0x19440605) + +/** @name RTCRPKIXSIGNATUREINT::uState values. + * @{ */ +/** Ready to go. */ +#define RTCRPKIXSIGNATURE_STATE_READY UINT32_C(1) +/** Need reset. */ +#define RTCRPKIXSIGNATURE_STATE_DONE UINT32_C(2) +/** Busted state, can happen after re-init. */ +#define RTCRPKIXSIGNATURE_STATE_BUSTED UINT32_C(3) +/** @} */ + + + +RTDECL(int) RTCrPkixSignatureCreate(PRTCRPKIXSIGNATURE phSignature, PCRTCRPKIXSIGNATUREDESC pDesc, void *pvOpaque, + bool fSigning, RTCRKEY hKey, PCRTASN1DYNTYPE pParams) +{ + /* + * Validate input. + */ + AssertPtrReturn(phSignature, VERR_INVALID_POINTER); + AssertPtrReturn(pDesc, VERR_INVALID_POINTER); + if (pParams) + { + AssertPtrReturn(pParams, VERR_INVALID_POINTER); + if ( pParams->enmType == RTASN1TYPE_NULL + || !RTASN1CORE_IS_PRESENT(&pParams->u.Core)) + pParams = NULL; + } + uint32_t cKeyRefs = RTCrKeyRetain(hKey); + AssertReturn(cKeyRefs != UINT32_MAX, VERR_INVALID_HANDLE); + + /* + * Instantiate the algorithm for the given operation. + */ + int rc = VINF_SUCCESS; + PRTCRPKIXSIGNATUREINT pThis = (PRTCRPKIXSIGNATUREINT)RTMemAllocZ(RT_UOFFSETOF_DYN(RTCRPKIXSIGNATUREINT, + abState[pDesc->cbState])); + if (pThis) + { + pThis->u32Magic = RTCRPKIXSIGNATUREINT_MAGIC; + pThis->cRefs = 1; + pThis->pDesc = pDesc; + pThis->fSigning = fSigning; + pThis->uState = RTCRPKIXSIGNATURE_STATE_READY; + pThis->hKey = hKey; + if (pDesc->pfnInit) + rc = pDesc->pfnInit(pDesc, pThis->abState, pvOpaque, fSigning, hKey, pParams); + if (RT_SUCCESS(rc)) + { + *phSignature = pThis; + return VINF_SUCCESS; + } + pThis->u32Magic = 0; + RTMemFree(pThis); + } + else + rc = VERR_NO_MEMORY; + RTCrKeyRelease(hKey); + return rc; + +} + + +RTDECL(uint32_t) RTCrPkixSignatureRetain(RTCRPKIXSIGNATURE hSignature) +{ + PRTCRPKIXSIGNATUREINT pThis = hSignature; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTCRPKIXSIGNATUREINT_MAGIC, UINT32_MAX); + + uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs); + Assert(cRefs < 64); + return cRefs; +} + + +/** + * Destructor worker. + */ +static uint32_t rtCrPkixSignatureDestructor(PRTCRPKIXSIGNATUREINT pThis) +{ + pThis->u32Magic = ~RTCRPKIXSIGNATUREINT_MAGIC; + if (pThis->pDesc->pfnDelete) + pThis->pDesc->pfnDelete(pThis->pDesc, pThis->abState, pThis->fSigning); + + RTCrKeyRelease(pThis->hKey); + pThis->hKey = NIL_RTCRKEY; + + size_t cbToWipe = RT_UOFFSETOF_DYN(RTCRPKIXSIGNATUREINT, abState[pThis->pDesc->cbState]); + RTMemWipeThoroughly(pThis, cbToWipe, 6); + + RTMemFree(pThis); + return 0; +} + + +RTDECL(uint32_t) RTCrPkixSignatureRelease(RTCRPKIXSIGNATURE hSignature) +{ + PRTCRPKIXSIGNATUREINT pThis = hSignature; + if (pThis == NIL_RTCRPKIXSIGNATURE) + return 0; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTCRPKIXSIGNATUREINT_MAGIC, UINT32_MAX); + + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + Assert(cRefs < 64); + if (!cRefs) + return rtCrPkixSignatureDestructor(pThis); + return cRefs; +} + + +/** + * Resets the signature provider instance prior to a new signing or verification + * opartion. + * + * @returns IPRT status code. + * @param pThis The instance to reset. + */ +static int rtCrPkxiSignatureReset(PRTCRPKIXSIGNATUREINT pThis) +{ + if (pThis->uState == RTCRPKIXSIGNATURE_STATE_DONE) + { + if (pThis->pDesc->pfnReset) + { + int rc = pThis->pDesc->pfnReset(pThis->pDesc, pThis->abState, pThis->fSigning); + if (RT_FAILURE(rc)) + { + pThis->uState = RTCRPKIXSIGNATURE_STATE_BUSTED; + return rc; + } + } + pThis->uState = RTCRPKIXSIGNATURE_STATE_READY; + } + return VINF_SUCCESS; +} + + +RTDECL(int) RTCrPkixSignatureVerify(RTCRPKIXSIGNATURE hSignature, RTCRDIGEST hDigest, + void const *pvSignature, size_t cbSignature) +{ + PRTCRPKIXSIGNATUREINT pThis = hSignature; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRPKIXSIGNATUREINT_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(!pThis->fSigning, VERR_INVALID_FUNCTION); + AssertReturn(pThis->uState == RTCRPKIXSIGNATURE_STATE_READY || pThis->uState == RTCRPKIXSIGNATURE_STATE_DONE, VERR_INVALID_STATE); + + uint32_t cRefs = RTCrDigestRetain(hDigest); + AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE); + + int rc = rtCrPkxiSignatureReset(pThis); + if (RT_SUCCESS(rc)) + { + rc = pThis->pDesc->pfnVerify(pThis->pDesc, pThis->abState, pThis->hKey, hDigest, pvSignature, cbSignature); + pThis->uState = RTCRPKIXSIGNATURE_STATE_DONE; + } + + RTCrDigestRelease(hDigest); + return rc; +} + + +RTDECL(int) RTCrPkixSignatureVerifyBitString(RTCRPKIXSIGNATURE hSignature, RTCRDIGEST hDigest, PCRTASN1BITSTRING pSignature) +{ + /* + * Just unpack it and pass it on to the lower level API. + */ + AssertPtrReturn(pSignature, VERR_INVALID_POINTER); + AssertReturn(RTAsn1BitString_IsPresent(pSignature), VERR_INVALID_PARAMETER); + uint32_t cbData = RTASN1BITSTRING_GET_BYTE_SIZE(pSignature); + void const *pvData = RTASN1BITSTRING_GET_BIT0_PTR(pSignature); + AssertPtrReturn(pvData, VERR_INVALID_PARAMETER); + + return RTCrPkixSignatureVerify(hSignature, hDigest, pvData, cbData); +} + + +RTDECL(int) RTCrPkixSignatureVerifyOctetString(RTCRPKIXSIGNATURE hSignature, RTCRDIGEST hDigest, PCRTASN1OCTETSTRING pSignature) +{ + /* + * Just unpack it and pass it on to the lower level API. + */ + AssertPtrReturn(pSignature, VERR_INVALID_POINTER); + AssertReturn(RTAsn1OctetString_IsPresent(pSignature), VERR_INVALID_PARAMETER); + uint32_t cbData = pSignature->Asn1Core.cb; + void const *pvData = pSignature->Asn1Core.uData.pv; + AssertPtrReturn(pvData, VERR_INVALID_PARAMETER); + + return RTCrPkixSignatureVerify(hSignature, hDigest, pvData, cbData); +} + + +RTDECL(int) RTCrPkixSignatureSign(RTCRPKIXSIGNATURE hSignature, RTCRDIGEST hDigest, + void *pvSignature, size_t *pcbSignature) +{ + PRTCRPKIXSIGNATUREINT pThis = hSignature; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRPKIXSIGNATUREINT_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(pThis->fSigning, VERR_INVALID_FUNCTION); + AssertReturn(pThis->uState == RTCRPKIXSIGNATURE_STATE_READY || pThis->uState == RTCRPKIXSIGNATURE_STATE_DONE, VERR_INVALID_STATE); + + uint32_t cRefs = RTCrDigestRetain(hDigest); + AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE); + + int rc = rtCrPkxiSignatureReset(pThis); + if (RT_SUCCESS(rc)) + { + rc = pThis->pDesc->pfnSign(pThis->pDesc, pThis->abState, pThis->hKey, hDigest, pvSignature, pcbSignature); + if (rc != VERR_BUFFER_OVERFLOW) + pThis->uState = RTCRPKIXSIGNATURE_STATE_DONE; + } + + RTCrDigestRelease(hDigest); + return rc; +} + diff --git a/src/VBox/Runtime/common/crypto/pkix-signature-rsa.cpp b/src/VBox/Runtime/common/crypto/pkix-signature-rsa.cpp new file mode 100644 index 00000000..3fa7c4ec --- /dev/null +++ b/src/VBox/Runtime/common/crypto/pkix-signature-rsa.cpp @@ -0,0 +1,557 @@ +/* $Id: pkix-signature-rsa.cpp $ */ +/** @file + * IPRT - Crypto - Public Key Signature Schema Algorithm, RSA Providers. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/rsa.h> + +#include <iprt/bignum.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/crypto/digest.h> +#include <iprt/crypto/pkix.h> + +#include "rsa-internal.h" +#include "pkix-signature-builtin.h" +#include "key-internal.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * RSA signature provider instance. + */ +typedef struct RTCRPKIXSIGNATURERSA +{ + /** Set if we're signing, clear if verifying. */ + bool fSigning; + + /** Temporary big number for use when signing or verifiying. */ + RTBIGNUM TmpBigNum1; + /** Temporary big number for use when signing or verifiying. */ + RTBIGNUM TmpBigNum2; + + /** Scratch space for decoding the key. */ + union + { + /** Public key. */ + RTCRRSAPUBLICKEY PublicKey; + /** Private key. */ + RTCRRSAPRIVATEKEY PrivateKey; + /** Scratch area where we assemble the signature. */ + uint8_t abSignature[RTCRRSA_MAX_MODULUS_BITS / 8 * 2]; + } Scratch; +} RTCRPKIXSIGNATURERSA; +/** Pointer to an RSA signature provider instance. */ +typedef RTCRPKIXSIGNATURERSA *PRTCRPKIXSIGNATURERSA; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** @name Pre-encoded DigestInfo DER sequences. + * @{ */ +static const uint8_t g_abMd2[] = +{/* { { 1.2.840.113549.2.2 (MD2), NULL }, hash octet-string } */ + 0x30,0x20, 0x30,0x0c, 0x06,0x08,0x2a,0x86,0x48,0x86,0xf7,0x0d,0x02,0x02, 0x05,0x00, 0x04,0x10 +}; +static const uint8_t g_abMd4[] = +{/* { { 1.2.840.113549.2.4 (MD4), NULL }, hash octet-string } */ + 0x30,0x20, 0x30,0x0c, 0x06,0x08,0x2a,0x86,0x48,0x86,0xf7,0x0d,0x02,0x04, 0x05,0x00, 0x04,0x10 +}; +static const uint8_t g_abMd5[] = +{/* { { 1.2.840.113549.2.5 (MD5), NULL }, hash octet-string } */ + 0x30,0x20, 0x30,0x0c, 0x06,0x08,0x2a,0x86,0x48,0x86,0xf7,0x0d,0x02,0x05, 0x05,0x00, 0x04,0x10 +}; +static const uint8_t g_abSha1[] = +{/* { { 1.3.14.3.2.26 (SHA-1), NULL }, hash octet-string } */ + 0x30,0x21, 0x30,0x09, 0x06,0x05,0x2b,0x0e,0x03,0x02,0x1a, 0x05,0x00, 0x04,0x14 +}; +static const uint8_t g_abSha256[] = +{/* { { 2.16.840.1.101.3.4.2.1 (SHA-256), NULL }, hash octet-string } */ + 0x30,0x31, 0x30,0x0d, 0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x01, 0x05,0x00, 0x04,0x20 +}; +static const uint8_t g_abSha384[] = +{/* { { 2.16.840.1.101.3.4.2.2 (SHA-384), NULL }, hash octet-string } */ + 0x30,0x41, 0x30,0x0d, 0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x02, 0x05,0x00, 0x04,0x30 +}; +static const uint8_t g_abSha512[] = +{/* { { 2.16.840.1.101.3.4.2.3 (SHA-512), NULL }, hash octet-string } */ + 0x30,0x51, 0x30,0x0d, 0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x03, 0x05,0x00, 0x04,0x40 +}; +static const uint8_t g_abSha224[] = +{/* { { 2.16.840.1.101.3.4.2.4 (SHA-224), NULL }, hash octet-string } */ + 0x30,0x2d, 0x30,0x0d, 0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x04, 0x05,0x00, 0x04,0x1c +}; +static const uint8_t g_abSha512t224[] = +{/* { { 2.16.840.1.101.3.4.2.5 (SHA-512T224), NULL }, hash octet-string } */ + 0x30,0x2d, 0x30,0x0d, 0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x05, 0x05,0x00, 0x04,0x1c +}; +static const uint8_t g_abSha512t256[] = +{/* { { 2.16.840.1.101.3.4.2.6 (SHA-512T256), NULL }, hash octet-string } */ + 0x30,0x31, 0x30,0x0d, 0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x06, 0x05,0x00, 0x04,0x20 +}; +static const uint8_t g_abSha3t224[] = +{/* { { 2.16.840.1.101.3.4.2.7 (SHA3-224), NULL }, hash octet-string } */ + 0x30,0x2d, 0x30,0x0d, 0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x07, 0x05,0x00, 0x04,0x1c +}; +static const uint8_t g_abSha3t256[] = +{/* { { 2.16.840.1.101.3.4.2.8 (SHA3-256), NULL }, hash octet-string } */ + 0x30,0x31, 0x30,0x0d, 0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x08, 0x05,0x00, 0x04,0x20 +}; +static const uint8_t g_abSha3t384[] = +{/* { { 2.16.840.1.101.3.4.2.9 (SHA3-384), NULL }, hash octet-string } */ + 0x30,0x41, 0x30,0x0d, 0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x09, 0x05,0x00, 0x04,0x30 +}; +static const uint8_t g_abSha3t512[] = +{/* { { 2.16.840.1.101.3.4.2.10 (SHA3-512), NULL }, hash octet-string } */ + 0x30,0x51, 0x30,0x0d, 0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x0a, 0x05,0x00, 0x04,0x40 +}; +/** @} */ + +/** Lookup array for the pre-encoded DigestInfo DER sequences. */ +static struct +{ + RTDIGESTTYPE enmDigest; + const uint8_t *pb; + size_t cb; +} const g_aDigestInfos[] = +{ + { RTDIGESTTYPE_SHA1, g_abSha1, sizeof(g_abSha1) }, + { RTDIGESTTYPE_SHA256, g_abSha256, sizeof(g_abSha256) }, + { RTDIGESTTYPE_SHA512, g_abSha512, sizeof(g_abSha512) }, + { RTDIGESTTYPE_MD2, g_abMd2, sizeof(g_abMd2) }, + { RTDIGESTTYPE_MD4, g_abMd4, sizeof(g_abMd4) }, + { RTDIGESTTYPE_MD5, g_abMd5, sizeof(g_abMd5) }, + { RTDIGESTTYPE_SHA384, g_abSha384, sizeof(g_abSha384) }, + { RTDIGESTTYPE_SHA224, g_abSha224, sizeof(g_abSha224) }, + { RTDIGESTTYPE_SHA512T224, g_abSha512t224, sizeof(g_abSha512t224)}, + { RTDIGESTTYPE_SHA512T256, g_abSha512t256, sizeof(g_abSha512t256)}, + { RTDIGESTTYPE_SHA3_224, g_abSha3t224, sizeof(g_abSha3t224) }, + { RTDIGESTTYPE_SHA3_256, g_abSha3t256, sizeof(g_abSha3t256) }, + { RTDIGESTTYPE_SHA3_384, g_abSha3t384, sizeof(g_abSha3t384) }, + { RTDIGESTTYPE_SHA3_512, g_abSha3t512, sizeof(g_abSha3t512) }, +}; + + +/** @impl_interface_method{RTCRPKIXSIGNATUREDESC,pfnInit} */ +static DECLCALLBACK(int) rtCrPkixSignatureRsa_Init(PCRTCRPKIXSIGNATUREDESC pDesc, void *pvState, void *pvOpaque, + bool fSigning, RTCRKEY hKey, PCRTASN1DYNTYPE pParams) +{ + RT_NOREF_PV(pDesc); RT_NOREF_PV(pvState); RT_NOREF_PV(pvOpaque); + + if (pParams) + return VERR_CR_PKIX_SIGNATURE_TAKES_NO_PARAMETERS; + + RTCRKEYTYPE enmKeyType = RTCrKeyGetType(hKey); + if (fSigning) + AssertReturn(enmKeyType == RTCRKEYTYPE_RSA_PRIVATE, VERR_CR_PKIX_NOT_RSA_PRIVATE_KEY); + else + AssertReturn(enmKeyType == RTCRKEYTYPE_RSA_PUBLIC, VERR_CR_PKIX_NOT_RSA_PUBLIC_KEY); + + PRTCRPKIXSIGNATURERSA pThis = (PRTCRPKIXSIGNATURERSA)pvState; + pThis->fSigning = fSigning; + + return VINF_SUCCESS; +} + + +/** @impl_interface_method{RTCRPKIXSIGNATUREDESC,pfnReset} */ +static DECLCALLBACK(int) rtCrPkixSignatureRsa_Reset(PCRTCRPKIXSIGNATUREDESC pDesc, void *pvState, bool fSigning) +{ + PRTCRPKIXSIGNATURERSA pThis = (PRTCRPKIXSIGNATURERSA)pvState; + RT_NOREF_PV(fSigning); RT_NOREF_PV(pDesc); + Assert(pThis->fSigning == fSigning); NOREF(pThis); + return VINF_SUCCESS; +} + + +/** @impl_interface_method{RTCRPKIXSIGNATUREDESC,pfnDelete} */ +static DECLCALLBACK(void) rtCrPkixSignatureRsa_Delete(PCRTCRPKIXSIGNATUREDESC pDesc, void *pvState, bool fSigning) +{ + PRTCRPKIXSIGNATURERSA pThis = (PRTCRPKIXSIGNATURERSA)pvState; + RT_NOREF_PV(fSigning); RT_NOREF_PV(pDesc); + Assert(pThis->fSigning == fSigning); + NOREF(pThis); +} + + +/** + * Common worker for rtCrPkixSignatureRsa_Verify and + * rtCrPkixSignatureRsa_Sign that encodes an EMSA-PKCS1-V1_5 signature in + * the scratch area. + * + * This function is referred to as EMSA-PKCS1-v1_5-ENCODE(M,k) in RFC-3447 and + * is described in section 9.2 + * + * @returns IPRT status code. + * @param pThis The RSA signature provider instance. + * @param hDigest The digest which hash to turn into a signature. + * @param cbEncodedMsg The desired encoded message length. + * @param fNoDigestInfo If true, skip the DigestInfo and encode the digest + * without any prefix like described in v1.5 (RFC-2313) + * and observed with RSA+MD5 signed timestamps. If + * false, include the prefix like v2.0 (RFC-2437) + * describes in step in section 9.2.1 + * (EMSA-PKCS1-v1_5) + * + * @remarks Must preserve informational status codes! + */ +static int rtCrPkixSignatureRsa_EmsaPkcs1V15Encode(PRTCRPKIXSIGNATURERSA pThis, RTCRDIGEST hDigest, size_t cbEncodedMsg, + bool fNoDigestInfo) +{ + AssertReturn(cbEncodedMsg * 2 <= sizeof(pThis->Scratch), VERR_CR_PKIX_INTERNAL_ERROR); + + /* + * Figure out which hash and select the associate prebaked DigestInfo. + */ + RTDIGESTTYPE const enmDigest = RTCrDigestGetType(hDigest); + AssertReturn(enmDigest != RTDIGESTTYPE_INVALID && enmDigest != RTDIGESTTYPE_UNKNOWN, VERR_CR_PKIX_UNKNOWN_DIGEST_TYPE); + uint8_t const *pbDigestInfoStart = NULL; + size_t cbDigestInfoStart = 0; + for (uint32_t i = 0; i < RT_ELEMENTS(g_aDigestInfos); i++) + if (g_aDigestInfos[i].enmDigest == enmDigest) + { + pbDigestInfoStart = g_aDigestInfos[i].pb; + cbDigestInfoStart = g_aDigestInfos[i].cb; + break; + } + if (!pbDigestInfoStart) + return VERR_CR_PKIX_UNKNOWN_DIGEST_TYPE; + + /* + * Get the hash size and verify that it matches what we've got in the + * precooked DigestInfo. ASSUMES less that 256 bytes of hash. + */ + uint32_t const cbHash = RTCrDigestGetHashSize(hDigest); + AssertReturn(cbHash > 0 && cbHash < _16K, VERR_OUT_OF_RANGE); + AssertReturn(cbHash == pbDigestInfoStart[cbDigestInfoStart - 1], VERR_CR_PKIX_INTERNAL_ERROR); + + if (fNoDigestInfo) + cbDigestInfoStart = 0; + + if (cbDigestInfoStart + cbHash + 11 > cbEncodedMsg) + return VERR_CR_PKIX_HASH_TOO_LONG_FOR_KEY; + + /* + * Encode the message the first part of the scratch area. + */ + uint8_t *pbDst = &pThis->Scratch.abSignature[0]; + pbDst[0] = 0x00; + pbDst[1] = 0x01; /* BT - block type, see RFC-2313. */ + size_t cbFFs = cbEncodedMsg - cbHash - cbDigestInfoStart - 3; + memset(&pbDst[2], 0xff, cbFFs); + pbDst += cbFFs + 2; + *pbDst++ = 0x00; + memcpy(pbDst, pbDigestInfoStart, cbDigestInfoStart); + pbDst += cbDigestInfoStart; + /* Note! Must preserve informational status codes from this call . */ + int rc = RTCrDigestFinal(hDigest, pbDst, cbHash); + if (RT_SUCCESS(rc)) + { + pbDst += cbHash; + Assert((size_t)(pbDst - &pThis->Scratch.abSignature[0]) == cbEncodedMsg); + } + return rc; +} + + + +/** @impl_interface_method{RTCRPKIXSIGNATUREDESC,pfnVerify} */ +static DECLCALLBACK(int) rtCrPkixSignatureRsa_Verify(PCRTCRPKIXSIGNATUREDESC pDesc, void *pvState, RTCRKEY hKey, + RTCRDIGEST hDigest, void const *pvSignature, size_t cbSignature) +{ + PRTCRPKIXSIGNATURERSA pThis = (PRTCRPKIXSIGNATURERSA)pvState; + RT_NOREF_PV(pDesc); + Assert(!pThis->fSigning); + if (cbSignature > sizeof(pThis->Scratch) / 2) + return VERR_CR_PKIX_SIGNATURE_TOO_LONG; + + /* + * Get the key bits we need. + */ + Assert(RTCrKeyGetType(hKey) == RTCRKEYTYPE_RSA_PUBLIC); + PRTBIGNUM pModulus = &hKey->u.RsaPublic.Modulus; + PRTBIGNUM pExponent = &hKey->u.RsaPublic.Exponent; + + /* + * 8.2.2.1 - Length check. (RFC-3447) + */ + if (cbSignature != RTBigNumByteWidth(pModulus)) + return VERR_CR_PKIX_INVALID_SIGNATURE_LENGTH; + + /* + * 8.2.2.2 - RSA verification / Decrypt the signature. + */ + /* a) s = OS2IP(S) -- Convert signature to integer. */ + int rc = RTBigNumInit(&pThis->TmpBigNum1, RTBIGNUMINIT_F_ENDIAN_BIG | RTBIGNUMINIT_F_UNSIGNED, + pvSignature, cbSignature); + if (RT_FAILURE(rc)) + return rc; + /* b) RSAVP1 - 5.2.2.2: Range check (0 <= s < n). */ + if (RTBigNumCompare(&pThis->TmpBigNum1, pModulus) < 0) + { + if (RTBigNumCompareWithU64(&pThis->TmpBigNum1, 0) >= 0) + { + /* b) RSAVP1 - 5.2.2.3: s^e mod n */ + rc = RTBigNumInitZero(&pThis->TmpBigNum2, 0); + if (RT_SUCCESS(rc)) + { + rc = RTBigNumModExp(&pThis->TmpBigNum2, &pThis->TmpBigNum1, pExponent, pModulus); + if (RT_SUCCESS(rc)) + { + /* c) EM' = I2OSP(m, k) -- Convert the result to bytes. */ + uint32_t cbDecrypted = RTBigNumByteWidth(&pThis->TmpBigNum2) + 1; /* 1 = leading zero byte */ + if (cbDecrypted <= sizeof(pThis->Scratch) / 2) + { + uint8_t *pbDecrypted = &pThis->Scratch.abSignature[sizeof(pThis->Scratch) / 2]; + rc = RTBigNumToBytesBigEndian(&pThis->TmpBigNum2, pbDecrypted, cbDecrypted); + if (RT_SUCCESS(rc)) + { + /* + * 8.2.2.3 - Build a hopefully identical signature using hDigest. + */ + rc = rtCrPkixSignatureRsa_EmsaPkcs1V15Encode(pThis, hDigest, cbDecrypted, false /* fNoDigestInfo */); + if (RT_SUCCESS(rc)) + { + /* + * 8.2.2.4 - Compare the two. + */ + if (memcmp(&pThis->Scratch.abSignature[0], pbDecrypted, cbDecrypted) == 0) + { /* No rc = VINF_SUCCESS here, mustpreserve informational status codes from digest. */ } + else + { + /* + * Try again without digestinfo. This style signing has been + * observed in Vista timestamp counter signatures (Thawte). + */ + rc = rtCrPkixSignatureRsa_EmsaPkcs1V15Encode(pThis, hDigest, cbDecrypted, + true /* fNoDigestInfo */); + if (RT_SUCCESS(rc)) + { + if (memcmp(&pThis->Scratch.abSignature[0], pbDecrypted, cbDecrypted) == 0) + { /* No rc = VINF_SUCCESS here, mustpreserve informational status codes from digest. */ } + else + rc = VERR_CR_PKIX_SIGNATURE_MISMATCH; + } + } + } + } + } + else + rc = VERR_CR_PKIX_SIGNATURE_TOO_LONG; + } + RTBigNumDestroy(&pThis->TmpBigNum2); + } + } + else + rc = VERR_CR_PKIX_SIGNATURE_NEGATIVE; + } + else + rc = VERR_CR_PKIX_SIGNATURE_GE_KEY; + RTBigNumDestroy(&pThis->TmpBigNum1); + return rc; +} + + +/** @impl_interface_method{RTCRPKIXSIGNATUREDESC,pfnSign} */ +static DECLCALLBACK(int) rtCrPkixSignatureRsa_Sign(PCRTCRPKIXSIGNATUREDESC pDesc, void *pvState, RTCRKEY hKey, + RTCRDIGEST hDigest, void *pvSignature, size_t *pcbSignature) +{ + PRTCRPKIXSIGNATURERSA pThis = (PRTCRPKIXSIGNATURERSA)pvState; + RT_NOREF_PV(pDesc); + Assert(pThis->fSigning); + + /* + * Get the key bits we need. + */ + Assert(RTCrKeyGetType(hKey) == RTCRKEYTYPE_RSA_PRIVATE); + PRTBIGNUM pModulus = &hKey->u.RsaPrivate.Modulus; + PRTBIGNUM pExponent = &hKey->u.RsaPrivate.PrivateExponent; + + /* + * Calc signature length and return if destination buffer isn't big enough. + */ + size_t const cbDst = *pcbSignature; + size_t const cbEncodedMsg = RTBigNumByteWidth(pModulus); + *pcbSignature = cbEncodedMsg; + if (cbEncodedMsg > sizeof(pThis->Scratch) / 2) + return VERR_CR_PKIX_SIGNATURE_TOO_LONG; + if (!pvSignature || cbDst < cbEncodedMsg) + return VERR_BUFFER_OVERFLOW; + + /* + * 8.1.1.1 - EMSA-PSS encoding. (RFC-3447) + */ + int rcRetSuccess; + int rc = rcRetSuccess = rtCrPkixSignatureRsa_EmsaPkcs1V15Encode(pThis, hDigest, cbEncodedMsg, false /* fNoDigestInfo */); + if (RT_SUCCESS(rc)) + { + /* + * 8.1.1.2 - RSA signature. + */ + /* a) m = OS2IP(EM) -- Convert the encoded message (EM) to integer. */ + rc = RTBigNumInit(&pThis->TmpBigNum1, RTBIGNUMINIT_F_ENDIAN_BIG | RTBIGNUMINIT_F_UNSIGNED, + pThis->Scratch.abSignature, cbEncodedMsg); + if (RT_SUCCESS(rc)) + { + /* b) s = RSASP1(K, m = EM) - 5.2.1.1: Range check (0 <= m < n). */ + if (RTBigNumCompare(&pThis->TmpBigNum1, pModulus) < 0) + { + /* b) s = RSAVP1(K, m = EM) - 5.2.1.2.a: s = m^d mod n */ + rc = RTBigNumInitZero(&pThis->TmpBigNum2, 0); + if (RT_SUCCESS(rc)) + { + rc = RTBigNumModExp(&pThis->TmpBigNum2, &pThis->TmpBigNum1, pExponent, pModulus); + if (RT_SUCCESS(rc)) + { + /* c) S = I2OSP(s, k) -- Convert the result to bytes. */ + rc = RTBigNumToBytesBigEndian(&pThis->TmpBigNum2, pvSignature, cbEncodedMsg); + AssertStmt(RT_SUCCESS(rc) || rc != VERR_BUFFER_OVERFLOW, rc = VERR_CR_PKIX_INTERNAL_ERROR); + + /* Make sure we return the informational status code from the digest on success. */ + if (rc == VINF_SUCCESS && rcRetSuccess != VINF_SUCCESS) + rc = rcRetSuccess; + } + RTBigNumDestroy(&pThis->TmpBigNum2); + } + } + else + rc = VERR_CR_PKIX_SIGNATURE_GE_KEY; + RTBigNumDestroy(&pThis->TmpBigNum1); + } + } + return rc; +} + + + + +/** RSA alias ODIs. */ +static const char * const g_apszHashWithRsaAliases[] = +{ + RTCR_PKCS1_MD2_WITH_RSA_OID, + RTCR_PKCS1_MD4_WITH_RSA_OID, + RTCR_PKCS1_MD5_WITH_RSA_OID, + RTCR_PKCS1_SHA1_WITH_RSA_OID, + RTCR_PKCS1_SHA256_WITH_RSA_OID, + RTCR_PKCS1_SHA384_WITH_RSA_OID, + RTCR_PKCS1_SHA512_WITH_RSA_OID, + RTCR_PKCS1_SHA224_WITH_RSA_OID, + RTCR_PKCS1_SHA512T224_WITH_RSA_OID, + RTCR_PKCS1_SHA512T256_WITH_RSA_OID, + /* Note: Note quite sure about these OIW oddballs. */ + "1.3.14.3.2.11" /* OIW rsaSignature */, + "1.3.14.3.2.14" /* OIW mdc2WithRSASignature */, + "1.3.14.3.2.15" /* OIW shaWithRSASignature */, + "1.3.14.3.2.24" /* OIW md2WithRSASignature */, + "1.3.14.3.2.25" /* OIW md5WithRSASignature */, + "1.3.14.3.2.29" /* OIW sha1WithRSASignature */, + NULL +}; + + +/** RSA descriptor. */ +DECL_HIDDEN_CONST(RTCRPKIXSIGNATUREDESC const) g_rtCrPkixSigningHashWithRsaDesc = +{ + "RSASSA-PKCS1-v1_5", + RTCR_PKCS1_RSA_OID, + g_apszHashWithRsaAliases, + sizeof(RTCRPKIXSIGNATURERSA), + 0, + 0, + rtCrPkixSignatureRsa_Init, + rtCrPkixSignatureRsa_Reset, + rtCrPkixSignatureRsa_Delete, + rtCrPkixSignatureRsa_Verify, + rtCrPkixSignatureRsa_Sign, +}; + + +/** + * Worker for RTCrRsaPublicKey_CanHandleDigestType and + * RTCrRsaPrivateKey_CanHandleDigestType. + * + * We implement these two functions here because we've already got the + * DigestInfo sizes nicely lined up here. + */ +static bool rtCrRsa_CanHandleDigestType(int32_t cModulusBits, RTDIGESTTYPE enmDigestType, PRTERRINFO pErrInfo) +{ + /* + * ASSUME EMSA-PKCS1-v1_5 padding scheme (RFC-8017 section 9.2): + * - 11 byte padding prefix (00, 01, 8 times ff) + * - digest info der sequence for rsaWithXxxxEncryption + * - the hash value. + */ + for (uint32_t i = 0; i < RT_ELEMENTS(g_aDigestInfos); i++) + if (g_aDigestInfos[i].enmDigest == enmDigestType) + { + size_t const cbHash = RTCrDigestTypeToHashSize(enmDigestType); + AssertBreak(cbHash > 0); + + size_t cbMsg = 11 + g_aDigestInfos[i].cb + cbHash; + if ((ssize_t)cbMsg <= cModulusBits / 8) + return true; + RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_INVALID_SIGNATURE_LENGTH, "cModulusBits=%d cbMsg=%u", cModulusBits, cbMsg); + return false; + } + RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_UNKNOWN_DIGEST_TYPE, "%s", RTCrDigestTypeToName(enmDigestType)); + return false; +} + + +RTDECL(bool) RTCrRsaPublicKey_CanHandleDigestType(PCRTCRRSAPUBLICKEY pRsaPublicKey, RTDIGESTTYPE enmDigestType, + PRTERRINFO pErrInfo) +{ + if (RTCrRsaPublicKey_IsPresent(pRsaPublicKey)) + return rtCrRsa_CanHandleDigestType(RTAsn1Integer_UnsignedLastBit(&pRsaPublicKey->Modulus) + 1, enmDigestType, pErrInfo); + return false; +} + + +RTDECL(bool) RTCrRsaPrivateKey_CanHandleDigestType(PCRTCRRSAPRIVATEKEY pRsaPrivateKey, RTDIGESTTYPE enmDigestType, + PRTERRINFO pErrInfo) +{ + if (RTCrRsaPrivateKey_IsPresent(pRsaPrivateKey)) + return rtCrRsa_CanHandleDigestType(RTAsn1Integer_UnsignedLastBit(&pRsaPrivateKey->Modulus) + 1, enmDigestType, pErrInfo); + return false; +} + diff --git a/src/VBox/Runtime/common/crypto/pkix-util.cpp b/src/VBox/Runtime/common/crypto/pkix-util.cpp new file mode 100644 index 00000000..4c5e56d5 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/pkix-util.cpp @@ -0,0 +1,155 @@ +/* $Id: pkix-util.cpp $ */ +/** @file + * IPRT - Crypto - Public Key Infrastructure API, Utilities. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/pkix.h> + +#include <iprt/asn1.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/string.h> +#include <iprt/crypto/rsa.h> + +#ifdef IPRT_WITH_OPENSSL +# include "internal/iprt-openssl.h" +# include "internal/openssl-pre.h" +# include <openssl/evp.h> +# include "internal/openssl-post.h" +#endif + + + + +RTDECL(const char *) RTCrPkixGetCiperOidFromSignatureAlgorithm(PCRTASN1OBJID pAlgorithm) +{ + /* + * This is all hardcoded, at least for the time being. + */ + if (RTAsn1ObjId_StartsWith(pAlgorithm, RTCR_PKCS1_OID)) + { + if (RTAsn1ObjIdCountComponents(pAlgorithm) == 7) + switch (RTAsn1ObjIdGetLastComponentsAsUInt32(pAlgorithm)) + { + case 2: + case 3: + case 4: + case 5: + case 11: + case 12: + case 13: + case 14: + return RTCR_PKCS1_RSA_OID; + case 1: AssertFailed(); + RT_FALL_THRU(); + default: + return NULL; + } + } + /* + * OIW oddballs. + */ + else if (RTAsn1ObjId_StartsWith(pAlgorithm, "1.3.14.3.2")) + { + if (RTAsn1ObjIdCountComponents(pAlgorithm) == 6) + switch (RTAsn1ObjIdGetLastComponentsAsUInt32(pAlgorithm)) + { + case 11: + case 14: + case 15: + case 24: + case 25: + case 29: + return RTCR_PKCS1_RSA_OID; + default: + return NULL; + } + } + + + return NULL; +} + + +RTDECL(bool) RTCrPkixPubKeyCanHandleDigestType(PCRTCRX509SUBJECTPUBLICKEYINFO pPublicKeyInfo, RTDIGESTTYPE enmDigestType, + PRTERRINFO pErrInfo) +{ + bool fRc = false; + if (RTCrX509SubjectPublicKeyInfo_IsPresent(pPublicKeyInfo)) + { + void const * const pvKeyBits = RTASN1BITSTRING_GET_BIT0_PTR(&pPublicKeyInfo->SubjectPublicKey); + uint32_t const cbKeyBits = RTASN1BITSTRING_GET_BYTE_SIZE(&pPublicKeyInfo->SubjectPublicKey); + RTASN1CURSORPRIMARY PrimaryCursor; + union + { + RTCRRSAPUBLICKEY RsaPublicKey; + } u; + + if (RTAsn1ObjId_CompareWithString(&pPublicKeyInfo->Algorithm.Algorithm, RTCR_PKCS1_RSA_OID) == 0) + { + /* + * RSA. + */ + RTAsn1CursorInitPrimary(&PrimaryCursor, pvKeyBits, cbKeyBits, pErrInfo, &g_RTAsn1DefaultAllocator, + RTASN1CURSOR_FLAGS_DER, "rsa"); + + RT_ZERO(u.RsaPublicKey); + int rc = RTCrRsaPublicKey_DecodeAsn1(&PrimaryCursor.Cursor, 0, &u.RsaPublicKey, "PublicKey"); + if (RT_SUCCESS(rc)) + fRc = RTCrRsaPublicKey_CanHandleDigestType(&u.RsaPublicKey, enmDigestType, pErrInfo); + RTCrRsaPublicKey_Delete(&u.RsaPublicKey); + } + else + { + RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_CIPHER_ALGO_NOT_KNOWN, "%s", pPublicKeyInfo->Algorithm.Algorithm.szObjId); + AssertMsgFailed(("unknown key algorithm: %s\n", pPublicKeyInfo->Algorithm.Algorithm.szObjId)); + fRc = true; + } + } + return fRc; +} + + +RTDECL(bool) RTCrPkixCanCertHandleDigestType(PCRTCRX509CERTIFICATE pCertificate, RTDIGESTTYPE enmDigestType, PRTERRINFO pErrInfo) +{ + if (RTCrX509Certificate_IsPresent(pCertificate)) + return RTCrPkixPubKeyCanHandleDigestType(&pCertificate->TbsCertificate.SubjectPublicKeyInfo, enmDigestType, pErrInfo); + return false; +} + diff --git a/src/VBox/Runtime/common/crypto/pkix-verify.cpp b/src/VBox/Runtime/common/crypto/pkix-verify.cpp new file mode 100644 index 00000000..22be047a --- /dev/null +++ b/src/VBox/Runtime/common/crypto/pkix-verify.cpp @@ -0,0 +1,319 @@ +/* $Id: pkix-verify.cpp $ */ +/** @file + * IPRT - Crypto - Public Key Infrastructure API, Verification. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/pkix.h> + +#include <iprt/err.h> +#include <iprt/string.h> +#include <iprt/crypto/digest.h> +#include <iprt/crypto/key.h> + +#ifdef IPRT_WITH_OPENSSL +# include "internal/iprt-openssl.h" +# include "internal/openssl-pre.h" +# include <openssl/evp.h> +# include "internal/openssl-post.h" +# ifndef OPENSSL_VERSION_NUMBER +# error "Missing OPENSSL_VERSION_NUMBER!" +# endif +#endif + + + +RTDECL(int) RTCrPkixPubKeyVerifySignature(PCRTASN1OBJID pAlgorithm, RTCRKEY hPublicKey, PCRTASN1DYNTYPE pParameters, + PCRTASN1BITSTRING pSignatureValue, const void *pvData, size_t cbData, + PRTERRINFO pErrInfo) +{ + /* + * Valid input. + */ + AssertPtrReturn(pAlgorithm, VERR_INVALID_POINTER); + AssertReturn(RTAsn1ObjId_IsPresent(pAlgorithm), VERR_INVALID_POINTER); + + if (pParameters) + { + AssertPtrReturn(pParameters, VERR_INVALID_POINTER); + if (pParameters->enmType == RTASN1TYPE_NULL) + pParameters = NULL; + } + + AssertPtrReturn(hPublicKey, VERR_INVALID_POINTER); + Assert(RTCrKeyHasPublicPart(hPublicKey)); + + AssertPtrReturn(pSignatureValue, VERR_INVALID_POINTER); + AssertReturn(RTAsn1BitString_IsPresent(pSignatureValue), VERR_INVALID_POINTER); + + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + AssertReturn(cbData > 0, VERR_INVALID_PARAMETER); + + /* + * Parameters are not currently supported (openssl code path). + */ + if (pParameters) + return RTErrInfoSet(pErrInfo, VERR_CR_PKIX_CIPHER_ALGO_PARAMS_NOT_IMPL, + "Cipher algorithm parameters are not yet supported."); + + /* + * Validate using IPRT. + */ + RTCRPKIXSIGNATURE hSignature; + int rcIprt = RTCrPkixSignatureCreateByObjId(&hSignature, pAlgorithm, hPublicKey, pParameters, false /*fSigning*/); + if (RT_FAILURE(rcIprt)) + return RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_CIPHER_ALGO_NOT_KNOWN, + "Unknown public key algorithm [IPRT]: %s", pAlgorithm->szObjId); + + RTCRDIGEST hDigest; + rcIprt = RTCrDigestCreateByObjId(&hDigest, pAlgorithm); + if (RT_SUCCESS(rcIprt)) + { + /* Calculate the digest. */ + rcIprt = RTCrDigestUpdate(hDigest, pvData, cbData); + if (RT_SUCCESS(rcIprt)) + { + rcIprt = RTCrPkixSignatureVerifyBitString(hSignature, hDigest, pSignatureValue); + if (RT_FAILURE(rcIprt)) + RTErrInfoSet(pErrInfo, rcIprt, "RTCrPkixSignatureVerifyBitString failed"); + } + else + RTErrInfoSet(pErrInfo, rcIprt, "RTCrDigestUpdate failed"); + RTCrDigestRelease(hDigest); + } + else + RTErrInfoSetF(pErrInfo, rcIprt, "Unknown digest algorithm [IPRT]: %s", pAlgorithm->szObjId); + RTCrPkixSignatureRelease(hSignature); + +#ifdef IPRT_WITH_OPENSSL + /* + * Validate using OpenSSL EVP. + */ + /* Create an EVP public key. */ + EVP_PKEY *pEvpPublicKey = NULL; + const EVP_MD *pEvpMdType = NULL; + int rcOssl = rtCrKeyToOpenSslKeyEx(hPublicKey, true /*fNeedPublic*/, pAlgorithm->szObjId, + (void **)&pEvpPublicKey, (const void **)&pEvpMdType, pErrInfo); + if (RT_SUCCESS(rcOssl)) + { + EVP_MD_CTX *pEvpMdCtx = EVP_MD_CTX_create(); + if (pEvpMdCtx) + { + if (EVP_VerifyInit_ex(pEvpMdCtx, pEvpMdType, NULL /*engine*/)) + { + /* Digest the data. */ + EVP_VerifyUpdate(pEvpMdCtx, pvData, cbData); + + /* Verify the signature. */ + if (EVP_VerifyFinal(pEvpMdCtx, + RTASN1BITSTRING_GET_BIT0_PTR(pSignatureValue), + RTASN1BITSTRING_GET_BYTE_SIZE(pSignatureValue), + pEvpPublicKey) > 0) + rcOssl = VINF_SUCCESS; + else + rcOssl = RTErrInfoSet(pErrInfo, VERR_CR_PKIX_OSSL_VERIFY_FINAL_FAILED, "EVP_VerifyFinal failed"); + + /* Cleanup and return: */ + } + else + rcOssl = RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_OSSL_CIPHER_ALOG_INIT_FAILED, + "EVP_VerifyInit_ex failed (algorithm type is %s)", pAlgorithm->szObjId); + EVP_MD_CTX_destroy(pEvpMdCtx); + } + else + rcOssl = RTErrInfoSetF(pErrInfo, VERR_NO_MEMORY, "EVP_MD_CTX_create failed"); + EVP_PKEY_free(pEvpPublicKey); + } + + /* + * Check the result. + */ + if ( (RT_SUCCESS(rcIprt) && RT_SUCCESS(rcOssl)) + || (RT_FAILURE_NP(rcIprt) && RT_FAILURE_NP(rcOssl)) + || (RT_SUCCESS(rcIprt) && rcOssl == VERR_CR_PKIX_OSSL_CIPHER_ALGO_NOT_KNOWN_EVP) ) + return rcIprt; + AssertMsgFailed(("rcIprt=%Rrc rcOssl=%Rrc\n", rcIprt, rcOssl)); + if (RT_FAILURE_NP(rcOssl)) + return rcOssl; +#endif /* IPRT_WITH_OPENSSL */ + + return rcIprt; +} + + +RTDECL(int) RTCrPkixPubKeyVerifySignedDigest(PCRTASN1OBJID pAlgorithm, RTCRKEY hPublicKey, PCRTASN1DYNTYPE pParameters, + void const *pvSignedDigest, size_t cbSignedDigest, RTCRDIGEST hDigest, + PRTERRINFO pErrInfo) +{ + /* + * Valid input. + */ + AssertPtrReturn(pAlgorithm, VERR_INVALID_POINTER); + AssertReturn(RTAsn1ObjId_IsPresent(pAlgorithm), VERR_INVALID_POINTER); + + if (pParameters) + { + AssertPtrReturn(pParameters, VERR_INVALID_POINTER); + if (pParameters->enmType == RTASN1TYPE_NULL) + pParameters = NULL; + } + + AssertPtrReturn(hPublicKey, VERR_INVALID_POINTER); + Assert(RTCrKeyHasPublicPart(hPublicKey)); + + AssertPtrReturn(pvSignedDigest, VERR_INVALID_POINTER); + AssertReturn(cbSignedDigest, VERR_INVALID_PARAMETER); + + AssertPtrReturn(hDigest, VERR_INVALID_HANDLE); + + /* + * Parameters are not currently supported (openssl code path). + */ + if (pParameters) + return RTErrInfoSet(pErrInfo, VERR_CR_PKIX_CIPHER_ALGO_PARAMS_NOT_IMPL, + "Cipher algorithm parameters are not yet supported."); + + /* + * Validate using IPRT. + */ + RTCRPKIXSIGNATURE hSignature; + int rcIprt = RTCrPkixSignatureCreateByObjId(&hSignature, pAlgorithm, hPublicKey, pParameters, false /*fSigning*/); + if (RT_FAILURE(rcIprt)) + return RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_CIPHER_ALGO_NOT_KNOWN, + "Unknown public key algorithm [IPRT]: %s", pAlgorithm->szObjId); + + rcIprt = RTCrPkixSignatureVerify(hSignature, hDigest, pvSignedDigest, cbSignedDigest); + if (RT_FAILURE(rcIprt)) + RTErrInfoSet(pErrInfo, rcIprt, "RTCrPkixSignatureVerifyBitString failed"); + + RTCrPkixSignatureRelease(hSignature); + +#if defined(IPRT_WITH_OPENSSL) \ + && (OPENSSL_VERSION_NUMBER > 0x10000000L) /* 0.9.8 doesn't seem to have EVP_PKEY_CTX_set_signature_md. */ + /* + * Validate using OpenSSL EVP. + */ + /* Combine encryption and digest if the algorithm doesn't specify the digest type. */ + const char *pszAlgObjId = pAlgorithm->szObjId; + if (!strcmp(pszAlgObjId, RTCRX509ALGORITHMIDENTIFIERID_RSA)) + { + pszAlgObjId = RTCrX509AlgorithmIdentifier_CombineEncryptionOidAndDigestOid(pszAlgObjId, + RTCrDigestGetAlgorithmOid(hDigest)); + AssertMsgStmt(pszAlgObjId, ("enc=%s hash=%s\n", pAlgorithm->szObjId, RTCrDigestGetAlgorithmOid(hDigest)), + pszAlgObjId = RTCrDigestGetAlgorithmOid(hDigest)); + } + + /* Create an EVP public key. */ + EVP_PKEY *pEvpPublicKey = NULL; + const EVP_MD *pEvpMdType = NULL; + int rcOssl = rtCrKeyToOpenSslKeyEx(hPublicKey, true /*fNeedPublic*/, pszAlgObjId, + (void **)&pEvpPublicKey, (const void **)&pEvpMdType, pErrInfo); + if (RT_SUCCESS(rcOssl)) + { + /* Create an EVP public key context we can use to validate the digest. */ + EVP_PKEY_CTX *pEvpPKeyCtx = EVP_PKEY_CTX_new(pEvpPublicKey, NULL); + if (pEvpPKeyCtx) + { + rcOssl = EVP_PKEY_verify_init(pEvpPKeyCtx); + if (rcOssl > 0) + { + rcOssl = EVP_PKEY_CTX_set_signature_md(pEvpPKeyCtx, pEvpMdType); + if (rcOssl > 0) + { + /* Get the digest from hDigest and verify it. */ + rcOssl = EVP_PKEY_verify(pEvpPKeyCtx, + (uint8_t const *)pvSignedDigest, + cbSignedDigest, + RTCrDigestGetHash(hDigest), + RTCrDigestGetHashSize(hDigest)); + if (rcOssl > 0) + rcOssl = VINF_SUCCESS; + else + rcOssl = RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_OSSL_VERIFY_FINAL_FAILED, + "EVP_PKEY_verify failed (%d)", rcOssl); + /* Cleanup and return: */ + } + else + rcOssl = RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_OSSL_EVP_PKEY_TYPE_ERROR, + "EVP_PKEY_CTX_set_signature_md failed (%d)", rcOssl); + } + else + rcOssl = RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_OSSL_EVP_PKEY_TYPE_ERROR, + "EVP_PKEY_verify_init failed (%d)", rcOssl); + EVP_PKEY_CTX_free(pEvpPKeyCtx); + } + else + rcOssl = RTErrInfoSet(pErrInfo, VERR_CR_PKIX_OSSL_EVP_PKEY_TYPE_ERROR, "EVP_PKEY_CTX_new failed"); + EVP_PKEY_free(pEvpPublicKey); + } + + /* + * Check the result. + */ + if ( (RT_SUCCESS(rcIprt) && RT_SUCCESS(rcOssl)) + || (RT_FAILURE_NP(rcIprt) && RT_FAILURE_NP(rcOssl)) + || (RT_SUCCESS(rcIprt) && rcOssl == VERR_CR_PKIX_OSSL_CIPHER_ALGO_NOT_KNOWN_EVP) ) + return rcIprt; + AssertMsgFailed(("rcIprt=%Rrc rcOssl=%Rrc\n", rcIprt, rcOssl)); + if (RT_FAILURE_NP(rcOssl)) + return rcOssl; +#endif /* IPRT_WITH_OPENSSL */ + + return rcIprt; +} + + +RTDECL(int) RTCrPkixPubKeyVerifySignedDigestByCertPubKeyInfo(PCRTCRX509SUBJECTPUBLICKEYINFO pCertPubKeyInfo, + void const *pvSignedDigest, size_t cbSignedDigest, + RTCRDIGEST hDigest, PRTERRINFO pErrInfo) +{ + RTCRKEY hPublicKey; + int rc = RTCrKeyCreateFromPublicAlgorithmAndBits(&hPublicKey, &pCertPubKeyInfo->Algorithm.Algorithm, + &pCertPubKeyInfo->SubjectPublicKey, pErrInfo, NULL); + if (RT_SUCCESS(rc)) + { + rc = RTCrPkixPubKeyVerifySignedDigest(&pCertPubKeyInfo->Algorithm.Algorithm, hPublicKey, + &pCertPubKeyInfo->Algorithm.Parameters, pvSignedDigest, cbSignedDigest, + hDigest, pErrInfo); + + uint32_t cRefs = RTCrKeyRelease(hPublicKey); + Assert(cRefs == 0); RT_NOREF(cRefs); + } + return rc; +} + diff --git a/src/VBox/Runtime/common/crypto/rc4-openssl.cpp b/src/VBox/Runtime/common/crypto/rc4-openssl.cpp new file mode 100644 index 00000000..2f63b0f0 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/rc4-openssl.cpp @@ -0,0 +1,75 @@ +/* $Id: rc4-openssl.cpp $ */ +/** @file + * IPRT - Crypto - Alleged RC4 via OpenSSL. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifdef IPRT_WITH_OPENSSL +# include "internal/iprt.h" +# include "internal/iprt-openssl.h" +# include "internal/openssl-pre.h" +# include <openssl/rc4.h> +# include "internal/openssl-post.h" +# include <iprt/crypto/rc4.h> + +# include <iprt/assert.h> +# include <iprt/assertcompile.h> + + + +RTDECL(void) RTCrRc4SetKey(PRTCRRC4KEY pKey, size_t cbData, void const *pvData) +{ + + AssertCompile(sizeof(RC4_INT) == 4 ? sizeof(pKey->au64Padding) == sizeof(pKey->Ossl) : sizeof(pKey->au64Padding) >= sizeof(pKey->Ossl)); + Assert((int)cbData == (ssize_t)cbData); + AssertPtr(pKey); + + RC4_set_key(&pKey->Ossl, (int)cbData, (const unsigned char *)pvData); +} + + +RTDECL(void) RTCrRc4(PRTCRRC4KEY pKey, size_t cbData, void const *pvDataIn, void *pvDataOut) +{ + AssertCompile(sizeof(RC4_INT) == 4 ? sizeof(pKey->au64Padding) == sizeof(pKey->Ossl) : sizeof(pKey->au64Padding) >= sizeof(pKey->Ossl)); + Assert((int)cbData == (ssize_t)cbData); + AssertPtr(pKey); + + RC4(&pKey->Ossl, (int)cbData, (const unsigned char *)pvDataIn, (unsigned char *)pvDataOut); +} + +#endif /* IPRT_WITH_OPENSSL */ + diff --git a/src/VBox/Runtime/common/crypto/rsa-asn1-decoder.cpp b/src/VBox/Runtime/common/crypto/rsa-asn1-decoder.cpp new file mode 100644 index 00000000..cad65bcc --- /dev/null +++ b/src/VBox/Runtime/common/crypto/rsa-asn1-decoder.cpp @@ -0,0 +1,53 @@ +/* $Id: rsa-asn1-decoder.cpp $ */ +/** @file + * IPRT - Crypto - RSA, Decoder for ASN.1. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/rsa.h> + +#include <iprt/errcore.h> +#include <iprt/string.h> + +#include "rsa-internal.h" + +/* + * Generate the code. + */ +#include <iprt/asn1-generator-asn1-decoder.h> + diff --git a/src/VBox/Runtime/common/crypto/rsa-core.cpp b/src/VBox/Runtime/common/crypto/rsa-core.cpp new file mode 100644 index 00000000..070b0153 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/rsa-core.cpp @@ -0,0 +1,54 @@ +/* $Id: rsa-core.cpp $ */ +/** @file + * IPRT - Crypto - RSA, Core APIs. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/rsa.h> + +#include <iprt/errcore.h> +#include <iprt/mem.h> +#include <iprt/string.h> + +#include "rsa-internal.h" + +/* + * Generate the code. + */ +#include <iprt/asn1-generator-core.h> + diff --git a/src/VBox/Runtime/common/crypto/rsa-init.cpp b/src/VBox/Runtime/common/crypto/rsa-init.cpp new file mode 100644 index 00000000..3dd37225 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/rsa-init.cpp @@ -0,0 +1,53 @@ +/* $Id: rsa-init.cpp $ */ +/** @file + * IPRT - Crypto - RSA, Initialization API. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/rsa.h> + +#include <iprt/errcore.h> +#include <iprt/string.h> + +#include "rsa-internal.h" + +/* + * Generate the code. + */ +#include <iprt/asn1-generator-init.h> + diff --git a/src/VBox/Runtime/common/crypto/rsa-internal.h b/src/VBox/Runtime/common/crypto/rsa-internal.h new file mode 100644 index 00000000..f0ae7bca --- /dev/null +++ b/src/VBox/Runtime/common/crypto/rsa-internal.h @@ -0,0 +1,50 @@ +/* $Id: rsa-internal.h $ */ +/** @file + * IPRT - Crypto - RSA, Internal Header. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef IPRT_INCLUDED_SRC_common_crypto_rsa_internal_h +#define IPRT_INCLUDED_SRC_common_crypto_rsa_internal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +/** The max number of bits we support in the modulus. */ +#define RTCRRSA_MAX_MODULUS_BITS 16384 + +#define RTASN1TMPL_TEMPLATE_FILE "../common/crypto/rsa-template.h" +#include <iprt/asn1-generator-internal-header.h> + +#endif /* !IPRT_INCLUDED_SRC_common_crypto_rsa_internal_h */ + diff --git a/src/VBox/Runtime/common/crypto/rsa-sanity.cpp b/src/VBox/Runtime/common/crypto/rsa-sanity.cpp new file mode 100644 index 00000000..4d3aa343 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/rsa-sanity.cpp @@ -0,0 +1,53 @@ +/* $Id: rsa-sanity.cpp $ */ +/** @file + * IPRT - Crypto - RSA, Sanity Checkers. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/rsa.h> + +#include <iprt/errcore.h> +#include <iprt/string.h> + +#include "rsa-internal.h" + +/* + * Generate the code. + */ +#include <iprt/asn1-generator-sanity.h> + diff --git a/src/VBox/Runtime/common/crypto/rsa-template.h b/src/VBox/Runtime/common/crypto/rsa-template.h new file mode 100644 index 00000000..a8e2001c --- /dev/null +++ b/src/VBox/Runtime/common/crypto/rsa-template.h @@ -0,0 +1,118 @@ +/* $Id: rsa-template.h $ */ +/** @file + * IPRT - Crypto - RSA, Code Generator Template. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#define RTASN1TMPL_DECL RTDECL + +/* + * RSA public key. + */ +#define RTASN1TMPL_TYPE RTCRRSAPUBLICKEY +#define RTASN1TMPL_EXT_NAME RTCrRsaPublicKey +#define RTASN1TMPL_INT_NAME rtCrRsaPublicKey +RTASN1TMPL_BEGIN_SEQCORE(); +RTASN1TMPL_MEMBER( Modulus, RTASN1INTEGER, RTAsn1Integer); +RTASN1TMPL_MEMBER( PublicExponent, RTASN1INTEGER, RTAsn1Integer); +RTASN1TMPL_END_SEQCORE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * One RSA other prime info. + */ +#define RTASN1TMPL_TYPE RTCRRSAOTHERPRIMEINFO +#define RTASN1TMPL_EXT_NAME RTCrRsaOtherPrimeInfo +#define RTASN1TMPL_INT_NAME rtCrRsaOtherPrimeInfo +RTASN1TMPL_BEGIN_SEQCORE(); +RTASN1TMPL_MEMBER( Prime, RTASN1INTEGER, RTAsn1Integer); +RTASN1TMPL_MEMBER( Exponent, RTASN1INTEGER, RTAsn1Integer); +RTASN1TMPL_MEMBER( Coefficient, RTASN1INTEGER, RTAsn1Integer); +RTASN1TMPL_END_SEQCORE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * Sequence of RSA other prime infos. + */ +#define RTASN1TMPL_TYPE RTCRRSAOTHERPRIMEINFOS +#define RTASN1TMPL_EXT_NAME RTCrRsaOtherPrimeInfos +#define RTASN1TMPL_INT_NAME rtCrRsaOtherPrimeInfos +RTASN1TMPL_SEQ_OF(RTCRRSAOTHERPRIMEINFO, RTCrRsaOtherPrimeInfo); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * RSA private key. + */ +#define RTASN1TMPL_TYPE RTCRRSAPRIVATEKEY +#define RTASN1TMPL_EXT_NAME RTCrRsaPrivateKey +#define RTASN1TMPL_INT_NAME rtCrRsaPrivateKey +RTASN1TMPL_BEGIN_SEQCORE(); +RTASN1TMPL_MEMBER( Version, RTASN1INTEGER, RTAsn1Integer); +RTASN1TMPL_MEMBER( Modulus, RTASN1INTEGER, RTAsn1Integer); +RTASN1TMPL_MEMBER( PublicExponent, RTASN1INTEGER, RTAsn1Integer); +RTASN1TMPL_MEMBER( PrivateExponent, RTASN1INTEGER, RTAsn1Integer); +RTASN1TMPL_MEMBER( Prime1, RTASN1INTEGER, RTAsn1Integer); +RTASN1TMPL_MEMBER( Prime2, RTASN1INTEGER, RTAsn1Integer); +RTASN1TMPL_MEMBER( Exponent1, RTASN1INTEGER, RTAsn1Integer); +RTASN1TMPL_MEMBER( Exponent2, RTASN1INTEGER, RTAsn1Integer); +RTASN1TMPL_MEMBER( Coefficient, RTASN1INTEGER, RTAsn1Integer); +RTASN1TMPL_MEMBER_OPT_ITAG_EX( OtherPrimeInfos, RTCRRSAOTHERPRIMEINFOS, RTCrRsaOtherPrimeInfos, ASN1_TAG_SEQUENCE, RTASN1TMPL_ITAG_F_UC, RT_NOTHING); +RTASN1TMPL_END_SEQCORE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * RSA Digest Info. + */ +#define RTASN1TMPL_TYPE RTCRRSADIGESTINFO +#define RTASN1TMPL_EXT_NAME RTCrRsaDigestInfo +#define RTASN1TMPL_INT_NAME rtCrRsaDigestInfo +RTASN1TMPL_BEGIN_SEQCORE(); +RTASN1TMPL_MEMBER( DigestAlgorithm, RTCRX509ALGORITHMIDENTIFIER, RTCrX509AlgorithmIdentifier); +RTASN1TMPL_MEMBER( Digest, RTASN1OCTETSTRING, RTAsn1OctetString); +RTASN1TMPL_END_SEQCORE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + diff --git a/src/VBox/Runtime/common/crypto/spc-asn1-decoder.cpp b/src/VBox/Runtime/common/crypto/spc-asn1-decoder.cpp new file mode 100644 index 00000000..e58fee49 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/spc-asn1-decoder.cpp @@ -0,0 +1,88 @@ +/* $Id: spc-asn1-decoder.cpp $ */ +/** @file + * IPRT - Crypto - Microsoft SPC / Authenticode, Decoder for ASN.1. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/spc.h> + +#include <iprt/errcore.h> +#include <iprt/string.h> +#include <iprt/uuid.h> + +#include "spc-internal.h" + + +/* + * One SPC Serialized Object Attribute. + */ + +/** Decode the content of the octet string value if known. */ +static int rtCrSpcSerializedObject_DecodeMore(PRTASN1CURSOR pCursor, uint32_t fFlags, + PRTCRSPCSERIALIZEDOBJECT pThis, const char *pszErrorTag) + +{ + RT_NOREF_PV(fFlags); RT_NOREF_PV(pszErrorTag); + + int rc; + RTASN1CURSOR SubCursor; + if (RTUuidCompareStr(pThis->Uuid.Asn1Core.uData.pUuid, RTCRSPCSERIALIZEDOBJECT_UUID_STR) == 0) + { + rc = RTAsn1MemAllocZ(&pThis->SerializedData.EncapsulatedAllocation, (void **)&pThis->u.pData, sizeof(*pThis->u.pData)); + if (RT_SUCCESS(rc)) + { + pThis->SerializedData.pEncapsulated = (PRTASN1CORE)pThis->u.pData; + pThis->enmType = RTCRSPCSERIALIZEDOBJECTTYPE_ATTRIBUTES; + + rc = RTAsn1CursorInitSubFromCore(pCursor, &pThis->SerializedData.Asn1Core, &SubCursor, "SerializedData"); + if (RT_SUCCESS(rc)) + rc = RTCrSpcSerializedObjectAttributes_DecodeAsn1(&SubCursor, 0, pThis->u.pData, "SD"); + } + } + else + return VINF_SUCCESS; + if (RT_SUCCESS(rc)) + rc = RTAsn1CursorCheckEnd(&SubCursor); + return rc; +} + +/* + * Generate the code. + */ +#include <iprt/asn1-generator-asn1-decoder.h> + diff --git a/src/VBox/Runtime/common/crypto/spc-core.cpp b/src/VBox/Runtime/common/crypto/spc-core.cpp new file mode 100644 index 00000000..4a956188 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/spc-core.cpp @@ -0,0 +1,94 @@ +/* $Id: spc-core.cpp $ */ +/** @file + * IPRT - Crypto - Microsoft SPC / Authenticode, Core APIs. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/spc.h> + +#include <iprt/errcore.h> +#include <iprt/string.h> +#include <iprt/uuid.h> + +#include "spc-internal.h" + + +RTDECL(int) RTCrSpcSerializedPageHashes_UpdateDerivedData(PRTCRSPCSERIALIZEDPAGEHASHES pThis) +{ + pThis->pData = (PCRTCRSPCPEIMAGEPAGEHASHES)pThis->RawData.Asn1Core.uData.pv; + return VINF_SUCCESS; +} + + +/* + * SPC Indirect Data Content. + */ + +RTDECL(PCRTCRSPCSERIALIZEDOBJECTATTRIBUTE) +RTCrSpcIndirectDataContent_GetPeImageObjAttrib(PCRTCRSPCINDIRECTDATACONTENT pThis, + RTCRSPCSERIALIZEDOBJECTATTRIBUTETYPE enmType) +{ + if (pThis->Data.enmType == RTCRSPCAAOVTYPE_PE_IMAGE_DATA) + { + Assert(RTAsn1ObjId_CompareWithString(&pThis->Data.Type, RTCRSPCPEIMAGEDATA_OID) == 0); + + if ( pThis->Data.uValue.pPeImage + && pThis->Data.uValue.pPeImage->T0.File.enmChoice == RTCRSPCLINKCHOICE_MONIKER + && RTCrSpcSerializedObject_IsPresent(pThis->Data.uValue.pPeImage->T0.File.u.pMoniker) ) + { + if (pThis->Data.uValue.pPeImage->T0.File.u.pMoniker->enmType == RTCRSPCSERIALIZEDOBJECTTYPE_ATTRIBUTES) + { + Assert(RTUuidCompareStr(pThis->Data.uValue.pPeImage->T0.File.u.pMoniker->Uuid.Asn1Core.uData.pUuid, + RTCRSPCSERIALIZEDOBJECT_UUID_STR) == 0); + PCRTCRSPCSERIALIZEDOBJECTATTRIBUTES pData = pThis->Data.uValue.pPeImage->T0.File.u.pMoniker->u.pData; + if (pData) + for (uint32_t i = 0; i < pData->cItems; i++) + if (pData->papItems[i]->enmType == enmType) + return pData->papItems[i]; + } + } + } + return NULL; +} + + +/* + * Generate the standard core code. + */ +#include <iprt/asn1-generator-core.h> + diff --git a/src/VBox/Runtime/common/crypto/spc-init.cpp b/src/VBox/Runtime/common/crypto/spc-init.cpp new file mode 100644 index 00000000..8c516e91 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/spc-init.cpp @@ -0,0 +1,55 @@ +/* $Id: spc-init.cpp $ */ +/** @file + * IPRT - Crypto - Microsoft SPC / Authenticode, Initialization API. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/spc.h> + +#include <iprt/errcore.h> +#include <iprt/string.h> +#include <iprt/uuid.h> + +#include "spc-internal.h" + + +/* + * Generate the standard core code. + */ +#include <iprt/asn1-generator-init.h> + diff --git a/src/VBox/Runtime/common/crypto/spc-internal.h b/src/VBox/Runtime/common/crypto/spc-internal.h new file mode 100644 index 00000000..21422dfd --- /dev/null +++ b/src/VBox/Runtime/common/crypto/spc-internal.h @@ -0,0 +1,47 @@ +/* $Id: spc-internal.h $ */ +/** @file + * IPRT - Crypto - Microsoft SPC / Authenticode, Internal Header. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef IPRT_INCLUDED_SRC_common_crypto_spc_internal_h +#define IPRT_INCLUDED_SRC_common_crypto_spc_internal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#define RTASN1TMPL_TEMPLATE_FILE "../common/crypto/spc-template.h" +#include <iprt/asn1-generator-internal-header.h> + +#endif /* !IPRT_INCLUDED_SRC_common_crypto_spc_internal_h */ + diff --git a/src/VBox/Runtime/common/crypto/spc-sanity.cpp b/src/VBox/Runtime/common/crypto/spc-sanity.cpp new file mode 100644 index 00000000..6c298506 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/spc-sanity.cpp @@ -0,0 +1,177 @@ +/* $Id: spc-sanity.cpp $ */ +/** @file + * IPRT - Crypto - Microsoft SPC / Authenticode, Sanity Checkers. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/spc.h> + +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/uuid.h> + +#include "spc-internal.h" + + +RTDECL(int) RTCrSpcIndirectDataContent_CheckSanityEx(PCRTCRSPCINDIRECTDATACONTENT pIndData, + PCRTCRPKCS7SIGNEDDATA pSignedData, + uint32_t fFlags, + PRTERRINFO pErrInfo) +{ + /* + * Match up the digest algorithms (page 8, v1.0). + */ + if (pSignedData->SignerInfos.cItems != 1) + return RTErrInfoSetF(pErrInfo, VERR_CR_SPC_NOT_EXACTLY_ONE_SIGNER_INFOS, + "SpcIndirectDataContent expects SignedData to have exactly one SignerInfos entries, found: %u", + pSignedData->SignerInfos.cItems); + if (pSignedData->DigestAlgorithms.cItems != 1) + return RTErrInfoSetF(pErrInfo, VERR_CR_SPC_NOT_EXACTLY_ONE_DIGEST_ALGO, + "SpcIndirectDataContent expects SignedData to have exactly one DigestAlgorithms entries, found: %u", + pSignedData->DigestAlgorithms.cItems); + + if (RTCrX509AlgorithmIdentifier_Compare(&pIndData->DigestInfo.DigestAlgorithm, /** @todo not entirely sure about this check... */ + &pSignedData->SignerInfos.papItems[0]->DigestAlgorithm) != 0) + return RTErrInfoSetF(pErrInfo, VERR_CR_SPC_SIGNED_IND_DATA_DIGEST_ALGO_MISMATCH, + "SpcIndirectDataContent DigestInfo and SignerInfos algorithms mismatch: %s vs %s", + pIndData->DigestInfo.DigestAlgorithm.Algorithm.szObjId, + pSignedData->SignerInfos.papItems[0]->DigestAlgorithm.Algorithm.szObjId); + + if (RTCrX509AlgorithmIdentifier_Compare(&pIndData->DigestInfo.DigestAlgorithm, + pSignedData->DigestAlgorithms.papItems[0]) != 0) + return RTErrInfoSetF(pErrInfo, VERR_CR_SPC_IND_DATA_DIGEST_ALGO_NOT_IN_DIGEST_ALGOS, + "SpcIndirectDataContent DigestInfo and SignedData.DigestAlgorithms[0] mismatch: %s vs %s", + pIndData->DigestInfo.DigestAlgorithm.Algorithm.szObjId, + pSignedData->DigestAlgorithms.papItems[0]->Algorithm.szObjId); + + if (fFlags & RTCRSPCINDIRECTDATACONTENT_SANITY_F_ONLY_KNOWN_HASH) + { + if (RTCrX509AlgorithmIdentifier_QueryDigestType(&pIndData->DigestInfo.DigestAlgorithm) == RTDIGESTTYPE_INVALID) + return RTErrInfoSetF(pErrInfo, VERR_CR_SPC_UNKNOWN_DIGEST_ALGO, + "SpcIndirectDataContent DigestAlgortihm is not known: %s", + pIndData->DigestInfo.DigestAlgorithm.Algorithm.szObjId); + } + + uint32_t cbDigest = RTCrX509AlgorithmIdentifier_QueryDigestSize(&pIndData->DigestInfo.DigestAlgorithm); + if ( pIndData->DigestInfo.Digest.Asn1Core.cb != cbDigest + && (cbDigest != UINT32_MAX || (fFlags & RTCRSPCINDIRECTDATACONTENT_SANITY_F_ONLY_KNOWN_HASH))) + return RTErrInfoSetF(pErrInfo, VERR_CR_SPC_IND_DATA_DIGEST_SIZE_MISMATCH, + "SpcIndirectDataContent Digest size mismatch with algorithm: %u, expected %u (%s)", + pIndData->DigestInfo.Digest.Asn1Core.cb, cbDigest, + pIndData->DigestInfo.DigestAlgorithm.Algorithm.szObjId); + + /* + * Data. + */ + if (fFlags & RTCRSPCINDIRECTDATACONTENT_SANITY_F_PE_IMAGE) + { + if ( pIndData->Data.enmType != RTCRSPCAAOVTYPE_PE_IMAGE_DATA + || RTAsn1ObjId_CompareWithString(&pIndData->Data.Type, RTCRSPCPEIMAGEDATA_OID) != 0) + return RTErrInfoSetF(pErrInfo, VERR_CR_SPC_EXPECTED_PE_IMAGE_DATA, + "SpcIndirectDataContent.Data.Type is %s, expected %s (SpcPeImageData) [enmType=%d]", + pIndData->Data.Type.szObjId, RTCRSPCPEIMAGEDATA_OID, pIndData->Data.enmType); + if ( pIndData->Data.uValue.pPeImage + || !RTCrSpcPeImageData_IsPresent(pIndData->Data.uValue.pPeImage) ) + return RTErrInfoSet(pErrInfo, VERR_CR_SPC_PEIMAGE_DATA_NOT_PRESENT, + "SpcIndirectDataContent.Data.uValue/PEImage is missing"); + + if ( pIndData->Data.uValue.pPeImage->T0.File.enmChoice == RTCRSPCLINKCHOICE_MONIKER + && RTCrSpcSerializedObject_IsPresent(pIndData->Data.uValue.pPeImage->T0.File.u.pMoniker) ) + { + PCRTCRSPCSERIALIZEDOBJECT pObj = pIndData->Data.uValue.pPeImage->T0.File.u.pMoniker; + + if (pObj->Uuid.Asn1Core.cb != sizeof(RTUUID)) + return RTErrInfoSetF(pErrInfo, VERR_CR_SPC_BAD_MONIKER_UUID, + "SpcIndirectDataContent...MonikerT1.Uuid incorrect size: %u, expected %u.", + pObj->Uuid.Asn1Core.cb, sizeof(RTUUID)); + if (RTUuidCompareStr(pObj->Uuid.Asn1Core.uData.pUuid, RTCRSPCSERIALIZEDOBJECT_UUID_STR) != 0) + return RTErrInfoSetF(pErrInfo, VERR_CR_SPC_UNKNOWN_MONIKER_UUID, + "SpcIndirectDataContent...MonikerT1.Uuid mismatch: %RTuuid, expected %s.", + pObj->Uuid.Asn1Core.uData.pUuid, RTCRSPCSERIALIZEDOBJECT_UUID_STR); + + if (pObj->enmType != RTCRSPCSERIALIZEDOBJECTTYPE_ATTRIBUTES) + return RTErrInfoSetF(pErrInfo, VERR_CR_SPC_BAD_MONIKER_CHOICE, + "SpcIndirectDataContent...pMoniker->enmType=%d, expected %d.", + pObj->enmType, RTCRSPCSERIALIZEDOBJECTTYPE_ATTRIBUTES); + if (!pObj->u.pData) + return RTErrInfoSet(pErrInfo, VERR_CR_SPC_MONIKER_BAD_DATA, + "SpcIndirectDataContent...pMoniker->pData is NULL."); + + uint32_t cPageHashTabs = 0; + for (uint32_t i = 0; i < pObj->u.pData->cItems; i++) + { + PCRTCRSPCSERIALIZEDOBJECTATTRIBUTE pAttr = pObj->u.pData->papItems[i]; + if ( RTAsn1ObjId_CompareWithString(&pAttr->Type, RTCRSPC_PE_IMAGE_HASHES_V1_OID) == 0 + || RTAsn1ObjId_CompareWithString(&pAttr->Type, RTCRSPC_PE_IMAGE_HASHES_V2_OID) == 0 ) + { + cPageHashTabs++; + AssertPtr(pAttr->u.pPageHashes->pData); + } + else + return RTErrInfoSetF(pErrInfo, VERR_CR_SPC_PEIMAGE_UNKNOWN_ATTRIBUTE, + "SpcIndirectDataContent...MonikerT1 unknown attribute %u: %s.", + i, pAttr->Type.szObjId); + } + if (cPageHashTabs > 0) + return RTErrInfoSetF(pErrInfo, VERR_CR_SPC_PEIMAGE_MULTIPLE_HASH_TABS, + "SpcIndirectDataContent...MonikerT1 multiple page hash attributes (%u).", cPageHashTabs); + + } + else if ( pIndData->Data.uValue.pPeImage->T0.File.enmChoice == RTCRSPCLINKCHOICE_FILE + && RTCrSpcString_IsPresent(&pIndData->Data.uValue.pPeImage->T0.File.u.pT2->File) ) + { + /* Could check for "<<<Obsolete>>>" here, but it's really irrelevant. */ + } + else if ( pIndData->Data.uValue.pPeImage->T0.File.enmChoice == RTCRSPCLINKCHOICE_URL + && RTAsn1String_IsPresent(pIndData->Data.uValue.pPeImage->T0.File.u.pUrl) ) + return RTErrInfoSet(pErrInfo, VERR_CR_SPC_PEIMAGE_URL_UNEXPECTED, + "SpcIndirectDataContent.Data.uValue.pPeImage->File is an URL, expected object Moniker or File."); + else + return RTErrInfoSet(pErrInfo, VERR_CR_SPC_PEIMAGE_NO_CONTENT, + "SpcIndirectDataContent.Data.uValue.pPeImage->File has no content"); + } + + return VINF_SUCCESS; +} + + +/* + * Generate the standard core code. + */ +#include <iprt/asn1-generator-sanity.h> + diff --git a/src/VBox/Runtime/common/crypto/spc-template.h b/src/VBox/Runtime/common/crypto/spc-template.h new file mode 100644 index 00000000..7479d09d --- /dev/null +++ b/src/VBox/Runtime/common/crypto/spc-template.h @@ -0,0 +1,198 @@ +/* $Id: spc-template.h $ */ +/** @file + * IPRT - Crypto - Microsoft SPC / Authenticode, Code Generator Template. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#define RTASN1TMPL_DECL RTDECL + + +/* + * One SPC Serialized Page Hashes V2 Object. + */ +#define RTASN1TMPL_TYPE RTCRSPCSERIALIZEDPAGEHASHES +#define RTASN1TMPL_EXT_NAME RTCrSpcSerializedPageHashes +#define RTASN1TMPL_INT_NAME rtCrSpcSerializedPageHashes +RTASN1TMPL_BEGIN_SETCORE(); +RTASN1TMPL_MEMBER( RawData, RTASN1OCTETSTRING, RTAsn1OctetString); +RTASN1TMPL_EXEC_DECODE( rc = RTCrSpcSerializedPageHashes_UpdateDerivedData(pThis) ) /* no ; */ +RTASN1TMPL_EXEC_CLONE( rc = RTCrSpcSerializedPageHashes_UpdateDerivedData(pThis) ) /* no ; */ +RTASN1TMPL_END_SETCORE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * One SPC Serialized Object Attribute. + */ +#define RTASN1TMPL_TYPE RTCRSPCSERIALIZEDOBJECTATTRIBUTE +#define RTASN1TMPL_EXT_NAME RTCrSpcSerializedObjectAttribute +#define RTASN1TMPL_INT_NAME rtCrSpcSerializedObjectAttribute +RTASN1TMPL_BEGIN_SEQCORE(); +RTASN1TMPL_MEMBER( Type, RTASN1OBJID, RTAsn1ObjId); +RTASN1TMPL_MEMBER_DYN_BEGIN( Type, RTCRSPCSERIALIZEDOBJECTATTRIBUTETYPE, enmType, Allocation); +RTASN1TMPL_MEMBER_DYN( u, pPageHashes, V1Hashes, RTCRSPCSERIALIZEDPAGEHASHES, RTCrSpcSerializedPageHashes, Allocation, + Type, enmType, RTCRSPCSERIALIZEDOBJECTATTRIBUTETYPE_PAGE_HASHES_V1, RTCRSPC_PE_IMAGE_HASHES_V1_OID); +RTASN1TMPL_MEMBER_DYN( u, pPageHashes, V2Hashes, RTCRSPCSERIALIZEDPAGEHASHES, RTCrSpcSerializedPageHashes, Allocation, + Type, enmType, RTCRSPCSERIALIZEDOBJECTATTRIBUTETYPE_PAGE_HASHES_V2, RTCRSPC_PE_IMAGE_HASHES_V2_OID); +RTASN1TMPL_MEMBER_DYN_DEFAULT( u, pCore, RTASN1CORE, RTAsn1Core, Allocation, + Type, enmType, RTCRSPCSERIALIZEDOBJECTATTRIBUTETYPE_UNKNOWN); +RTASN1TMPL_MEMBER_DYN_END( Type, RTCRSPCSERIALIZEDOBJECTATTRIBUTETYPE, enmType, Allocation); +RTASN1TMPL_END_SEQCORE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + +/* + * Set of SPC Serialized Object Attributes. + */ +#define RTASN1TMPL_TYPE RTCRSPCSERIALIZEDOBJECTATTRIBUTES +#define RTASN1TMPL_EXT_NAME RTCrSpcSerializedObjectAttributes +#define RTASN1TMPL_INT_NAME rtCrSpcSerializedObjectAttributes +RTASN1TMPL_SET_OF(RTCRSPCSERIALIZEDOBJECTATTRIBUTE, RTCrSpcSerializedObjectAttribute); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * One SPC Serialized Object. + */ +#define RTASN1TMPL_TYPE RTCRSPCSERIALIZEDOBJECT +#define RTASN1TMPL_EXT_NAME RTCrSpcSerializedObject +#define RTASN1TMPL_INT_NAME rtCrSpcSerializedObject +RTASN1TMPL_BEGIN_SEQCORE(); +RTASN1TMPL_MEMBER_EX( Uuid, RTASN1OCTETSTRING, RTAsn1OctetString, + RTASN1TMPL_MEMBER_CONSTR_MIN_MAX(Uuid, RTASN1OCTETSTRING, RTAsn1OctetString, 16, 16, RT_NOTHING)); +RTASN1TMPL_MEMBER( SerializedData, RTASN1OCTETSTRING, RTAsn1OctetString); +RTASN1TMPL_EXEC_DECODE( rc = rtCrSpcSerializedObject_DecodeMore(pCursor, fFlags, pThis, pszErrorTag) ) /* no ; */ +RTASN1TMPL_END_SEQCORE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * Choosy SPC strings. + */ +#define RTASN1TMPL_TYPE RTCRSPCSTRING +#define RTASN1TMPL_EXT_NAME RTCrSpcString +#define RTASN1TMPL_INT_NAME rtCrSpcString +RTASN1TMPL_BEGIN_PCHOICE(); +RTASN1TMPL_PCHOICE_ITAG_CP( 0, RTCRSPCSTRINGCHOICE_UCS2, u.pUcs2, Ucs2, RTASN1STRING, RTAsn1BmpString); +RTASN1TMPL_PCHOICE_ITAG_CP( 1, RTCRSPCSTRINGCHOICE_ASCII, u.pAscii, Ascii, RTASN1STRING, RTAsn1Ia5String); +RTASN1TMPL_END_PCHOICE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * SPC Link. + */ +#define RTASN1TMPL_TYPE RTCRSPCLINK +#define RTASN1TMPL_EXT_NAME RTCrSpcLink +#define RTASN1TMPL_INT_NAME rtCrSpcLink +RTASN1TMPL_BEGIN_PCHOICE(); +RTASN1TMPL_PCHOICE_ITAG_CP( 0, RTCRSPCLINKCHOICE_URL, u.pUrl, Url, RTASN1STRING, RTAsn1Ia5String); +RTASN1TMPL_PCHOICE_ITAG( 1, RTCRSPCLINKCHOICE_MONIKER, u.pMoniker, Moniker, RTCRSPCSERIALIZEDOBJECT, RTCrSpcSerializedObject); +RTASN1TMPL_PCHOICE_XTAG( 2, RTCRSPCLINKCHOICE_FILE, u.pT2, CtxTag2, File, RTCRSPCSTRING, RTCrSpcString); +RTASN1TMPL_END_PCHOICE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * SPC PE Image Data. + * + * Note! This is not correctly declared in available specifications. The file + * member is tagged. Seeing the '--#public--' comment in the specs, + * one can't only guess that there are other alternatives in that part + * of the structure that microsoft does not wish to document. + */ +#define RTASN1TMPL_TYPE RTCRSPCPEIMAGEDATA +#define RTASN1TMPL_EXT_NAME RTCrSpcPeImageData +#define RTASN1TMPL_INT_NAME rtCrSpcPeImageData +RTASN1TMPL_BEGIN_SEQCORE(); +/** @todo The flags defaults to includeResources. Could be expressed here rather + * than left to the user to deal with. */ +RTASN1TMPL_MEMBER_OPT_ITAG_EX( Flags, RTASN1BITSTRING, RTAsn1BitString, ASN1_TAG_BIT_STRING, RTASN1TMPL_ITAG_F_UP, + RTASN1TMPL_MEMBER_CONSTR_BITSTRING_MIN_MAX(Flags, 0, 3, RT_NOTHING)); +RTASN1TMPL_MEMBER_OPT_XTAG_EX( T0, CtxTag0, File, RTCRSPCLINK, RTCrSpcLink, 0, \ + RTASN1TMPL_MEMBER_CONSTR_PRESENT(T0.File, RTCrSpcLink, RT_NOTHING)); +RTASN1TMPL_END_SEQCORE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * SPC Attribute Type And Optional Value. + * + * Note! The value doesn't look very optional in available examples and specs. + * The available specs also claim there is an explicit 0 tag around the + * data, which isn't there is in signed executables. Gotta love Microsoft... + */ +#define RTASN1TMPL_TYPE RTCRSPCATTRIBUTETYPEANDOPTIONALVALUE +#define RTASN1TMPL_EXT_NAME RTCrSpcAttributeTypeAndOptionalValue +#define RTASN1TMPL_INT_NAME rtCrSpcAttributeTypeAndOptionalValue +RTASN1TMPL_BEGIN_SEQCORE(); +RTASN1TMPL_MEMBER( Type, RTASN1OBJID, RTAsn1ObjId); +RTASN1TMPL_MEMBER_DYN_BEGIN( Type, RTCRSPCAAOVTYPE, enmType, Allocation); +RTASN1TMPL_MEMBER_DYN( uValue, pPeImage, PeImage, RTCRSPCPEIMAGEDATA, RTCrSpcPeImageData, Allocation, + Type, enmType, RTCRSPCAAOVTYPE_PE_IMAGE_DATA, RTCRSPCPEIMAGEDATA_OID); +RTASN1TMPL_MEMBER_DYN_DEFAULT( uValue, pCore, RTASN1CORE, RTAsn1Core, Allocation, + Type, enmType, RTCRSPCAAOVTYPE_UNKNOWN); +RTASN1TMPL_MEMBER_DYN_END( Type, RTCRSPCAAOVTYPE, enmType, Allocation); +RTASN1TMPL_END_SEQCORE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * SPC Indirect Data Content. + */ +#define RTASN1TMPL_TYPE RTCRSPCINDIRECTDATACONTENT +#define RTASN1TMPL_EXT_NAME RTCrSpcIndirectDataContent +#define RTASN1TMPL_INT_NAME rtCrSpcIndirectDataContent +RTASN1TMPL_BEGIN_SEQCORE(); +RTASN1TMPL_MEMBER( Data, RTCRSPCATTRIBUTETYPEANDOPTIONALVALUE, RTCrSpcAttributeTypeAndOptionalValue); +RTASN1TMPL_MEMBER( DigestInfo, RTCRPKCS7DIGESTINFO, RTCrPkcs7DigestInfo); +RTASN1TMPL_END_SEQCORE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + diff --git a/src/VBox/Runtime/common/crypto/ssl-openssl.cpp b/src/VBox/Runtime/common/crypto/ssl-openssl.cpp new file mode 100644 index 00000000..ee61034a --- /dev/null +++ b/src/VBox/Runtime/common/crypto/ssl-openssl.cpp @@ -0,0 +1,507 @@ +/** @file + * IPRT - Crypto - Secure Socket Layer (SSL) / Transport Security Layer (TLS). + */ + +/* + * Copyright (C) 2018-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ +#ifdef IPRT_WITH_OPENSSL /* whole file */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +# include "internal/iprt.h" +# include <iprt/crypto/ssl.h> + +# include <iprt/asm.h> +# include <iprt/assert.h> +# include <iprt/err.h> +# include <iprt/file.h> +# include <iprt/mem.h> +# include <iprt/string.h> + +# include "internal/magics.h" + +# include "internal/iprt-openssl.h" +# include "internal/openssl-pre.h" +# include <openssl/ssl.h> +# include <openssl/tls1.h> +# include "internal/openssl-post.h" + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +/** + * SSL instance data for OpenSSL. + */ +typedef struct RTCRSSLINT +{ + /** Magic value (RTCRSSLINT_MAGIC). */ + uint32_t u32Magic; + /** Reference count. */ + uint32_t volatile cRefs; + /** The SSL context. */ + SSL_CTX *pCtx; +} RTCRSSLINT; + +/** + * SSL session instance data for OpenSSL. + */ +typedef struct RTCRSSLSESSIONINT +{ + /** Magic value (RTCRSSLSESSIONINT_MAGIC). */ + uint32_t u32Magic; + /** Reference count. */ + uint32_t volatile cRefs; + /** RTCRSSLSESSION_F_XXX. */ + uint32_t fFlags; + + /** The SSL instance. */ + SSL *pSsl; + /** The socket BIO instance. */ + BIO *pBio; +} RTCRSSLSESSIONINT; + + + +RTDECL(int) RTCrSslCreate(PRTCRSSL phSsl, uint32_t fFlags) +{ + AssertPtr(phSsl); + *phSsl = NIL_RTCRSSL; + AssertReturn(!fFlags, VERR_INVALID_FLAGS); + + SSL_library_init(); + + /* + * We aim at TLSv1 or higher here by default. + */ +# if OPENSSL_VERSION_NUMBER >= 0x10100000 + const SSL_METHOD *pSslMethod = TLS_method(); +# elif OPENSSL_VERSION_NUMBER >= 0x10002000 + const SSL_METHOD *pSslMethod = SSLv23_method(); +# elif OPENSSL_VERSION_NUMBER >= 0x10000000 + const SSL_METHOD *pSslMethod = TLSv1_method(); +# else + SSL_METHOD *pSslMethod = TLSv1_method(); +# endif + if (pSslMethod) + { + RTCRSSLINT *pThis = (RTCRSSLINT *)RTMemAllocZ(sizeof(*pThis)); + if (pThis) + { + pThis->pCtx = SSL_CTX_new(pSslMethod); + if (pThis->pCtx) + { + /* Help with above aim. */ +# if OPENSSL_VERSION_NUMBER >= 0x10100000 +# ifndef SSL_CTX_get_min_proto_version +/* Some older OpenSSL 1.1.0 releases lack the getters, officially they were + * added with 1.1.1 but someone cherry picked them, just maybe too late. */ +# define SSL_CTX_get_min_proto_version(ctx) (0) +# endif + if (SSL_CTX_get_min_proto_version(pThis->pCtx) < TLS1_VERSION) + SSL_CTX_set_min_proto_version(pThis->pCtx, TLS1_VERSION); +# elif OPENSSL_VERSION_NUMBER >= 0x10002000 + SSL_CTX_set_options(pThis->pCtx, SSL_OP_NO_SSLv2); + SSL_CTX_set_options(pThis->pCtx, SSL_OP_NO_SSLv3); +# endif + + /* + * Complete the instance and return it. + */ + pThis->u32Magic = RTCRSSLINT_MAGIC; + pThis->cRefs = 1; + + *phSsl = pThis; + return VINF_SUCCESS; + } + + RTMemFree(pThis); + } + return VERR_NO_MEMORY; + } + return VERR_NOT_SUPPORTED; +} + + +RTDECL(uint32_t) RTCrSslRetain(RTCRSSL hSsl) +{ + RTCRSSLINT *pThis = hSsl; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTCRSSLINT_MAGIC, UINT32_MAX); + + uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs); + Assert(cRefs > 1); + Assert(cRefs < 1024); + return cRefs; +} + + +/** + * Worker for RTCrSslRelease. + */ +static int rtCrSslDestroy(RTCRSSLINT *pThis) +{ + ASMAtomicWriteU32(&pThis->u32Magic, ~RTCRSSLINT_MAGIC); + SSL_CTX_free(pThis->pCtx); + pThis->pCtx = NULL; + RTMemFree(pThis); + return 0; +} + + +RTDECL(uint32_t) RTCrSslRelease(RTCRSSL hSsl) +{ + RTCRSSLINT *pThis = hSsl; + if (pThis == NIL_RTCRSSL) + return 0; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTCRSSLINT_MAGIC, UINT32_MAX); + + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + Assert(cRefs < 1024); + if (cRefs == 0) + return rtCrSslDestroy(pThis); + return cRefs; +} + + +RTDECL(int) RTCrSslSetCertificateFile(RTCRSSL hSsl, const char *pszFile, uint32_t fFlags) +{ + RTCRSSLINT *pThis = hSsl; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRSSLINT_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(!(fFlags & ~RTCRSSL_FILE_F_ASN1), VERR_INVALID_FLAGS); + + int rcOssl = SSL_CTX_use_certificate_file(pThis->pCtx, pszFile, + RTCRSSL_FILE_F_ASN1 & fFlags ? SSL_FILETYPE_ASN1 : SSL_FILETYPE_PEM); + if (rcOssl != 0) + return VINF_SUCCESS; + return !pszFile || !*pszFile || !RTFileExists(pszFile) ? VERR_FILE_NOT_FOUND : VERR_OPEN_FAILED; /** @todo Better status codes */ +} + + +RTDECL(int) RTCrSslSetPrivateKeyFile(RTCRSSL hSsl, const char *pszFile, uint32_t fFlags) +{ + RTCRSSLINT *pThis = hSsl; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRSSLINT_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(!(fFlags & ~RTCRSSL_FILE_F_ASN1), VERR_INVALID_FLAGS); + + int rcOssl = SSL_CTX_use_PrivateKey_file(pThis->pCtx, pszFile, + RTCRSSL_FILE_F_ASN1 & fFlags ? SSL_FILETYPE_ASN1 : SSL_FILETYPE_PEM); + if (rcOssl != 0) + return VINF_SUCCESS; + return !pszFile || !*pszFile || !RTFileExists(pszFile) ? VERR_FILE_NOT_FOUND : VERR_OPEN_FAILED; /** @todo Better status codes */ +} + + +RTDECL(int) RTCrSslLoadTrustedRootCerts(RTCRSSL hSsl, const char *pszFile, const char *pszDir) +{ + RTCRSSLINT *pThis = hSsl; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRSSLINT_MAGIC, VERR_INVALID_HANDLE); + + int rcOssl = SSL_CTX_load_verify_locations(pThis->pCtx, pszFile, pszDir); + if (rcOssl != 0) + return VINF_SUCCESS; + + if (!pszFile || !*pszFile || !RTFileExists(pszFile)) + return VERR_FILE_NOT_FOUND; + return VERR_OPEN_FAILED; /** @todo Better status codes */ +} + + +RTDECL(int) RTCrSslSetNoPeerVerify(RTCRSSL hSsl) +{ + RTCRSSLINT *pThis = hSsl; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRSSLINT_MAGIC, VERR_INVALID_HANDLE); + + SSL_CTX_set_verify(pThis->pCtx, SSL_VERIFY_NONE, NULL); + return VINF_SUCCESS; +} + + + +//RTDECL(int) RTCrSslCreateSession(RTCRSSL hSsl, RTSOCKET hSocket, uint32_t fFlags, PRTCRSSLSESSION phSslConn); + +RTDECL(int) RTCrSslCreateSessionForNativeSocket(RTCRSSL hSsl, RTHCINTPTR hNativeSocket, uint32_t fFlags, + PRTCRSSLSESSION phSslSession) +{ + /* + * Validate input. + */ + *phSslSession = NIL_RTCRSSLSESSION; + + RTCRSSLINT *pThis = hSsl; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRSSLINT_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(!(fFlags & ~RTCRSSLSESSION_F_NON_BLOCKING), VERR_INVALID_FLAGS); + + /* + * Create a new session. + */ + int rc = VERR_NO_MEMORY; + RTCRSSLSESSIONINT *pSession = (RTCRSSLSESSIONINT *)RTMemAllocZ(sizeof(*pSession)); + if (pSession) + { + pSession->pSsl = SSL_new(pThis->pCtx); + if (pSession->pSsl) + { + /* Disable read-ahead if non-blocking socket relying on select/poll. */ + if (fFlags & RTCRSSLSESSION_F_NON_BLOCKING) + SSL_set_read_ahead(pSession->pSsl, 0); + + /* Create a wrapper for the socket handle. */ + pSession->pBio = BIO_new_socket(hNativeSocket, BIO_NOCLOSE); + if (pSession->pBio) + { +# if (OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(LIBRESSL_VERSION_NUMBER)) || LIBRESSL_VERSION_NUMBER >= 0x2070000f + BIO_up_ref(pSession->pBio); /* our reference. */ +# endif + SSL_set_bio(pSession->pSsl, pSession->pBio, pSession->pBio); + + /* + * Done. + */ + pSession->cRefs = 1; + pSession->u32Magic = RTCRSSLSESSIONINT_MAGIC; + *phSslSession = pSession; + return VINF_SUCCESS; + } + + SSL_free(pSession->pSsl); + pSession->pSsl = NULL; + } + RTMemFree(pSession); + } + return rc; +} + + +/********************************************************************************************************************************* +* Session implementation. * +*********************************************************************************************************************************/ + +RTDECL(uint32_t) RTCrSslSessionRetain(RTCRSSLSESSION hSslSession) +{ + RTCRSSLSESSIONINT *pThis = hSslSession; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTCRSSLSESSIONINT_MAGIC, UINT32_MAX); + + uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs); + Assert(cRefs > 1); + Assert(cRefs < 1024); + return cRefs; +} + + +/** + * Worker for RTCrSslRelease. + */ +static int rtCrSslSessionDestroy(RTCRSSLSESSIONINT *pThis) +{ + ASMAtomicWriteU32(&pThis->u32Magic, ~RTCRSSLSESSIONINT_MAGIC); + SSL_free(pThis->pSsl); + pThis->pSsl = NULL; +# if (OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(LIBRESSL_VERSION_NUMBER)) || LIBRESSL_VERSION_NUMBER >= 0x2070000f + BIO_free(pThis->pBio); +# endif + pThis->pBio = NULL; + RTMemFree(pThis); + return 0; +} + + +RTDECL(uint32_t) RTCrSslSessionRelease(RTCRSSLSESSION hSslSession) +{ + RTCRSSLSESSIONINT *pThis = hSslSession; + if (pThis == NIL_RTCRSSLSESSION) + return 0; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTCRSSLSESSIONINT_MAGIC, UINT32_MAX); + + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + Assert(cRefs < 1024); + if (cRefs == 0) + return rtCrSslSessionDestroy(pThis); + return cRefs; +} + + +RTDECL(int) RTCrSslSessionAccept(RTCRSSLSESSION hSslSession, uint32_t fFlags) +{ + RTCRSSLSESSIONINT *pThis = hSslSession; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRSSLSESSIONINT_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(!fFlags, VERR_INVALID_FLAGS); + + int rcOssl = SSL_accept(pThis->pSsl); + if (rcOssl > 0) + return VINF_SUCCESS; + + /** @todo better status codes. */ + if (BIO_should_retry(pThis->pBio)) + return VERR_TRY_AGAIN; + return VERR_NOT_SUPPORTED; +} + + +RTDECL(int) RTCrSslSessionConnect(RTCRSSLSESSION hSslSession, uint32_t fFlags) +{ + RTCRSSLSESSIONINT *pThis = hSslSession; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRSSLSESSIONINT_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(!fFlags, VERR_INVALID_FLAGS); + + int rcOssl = SSL_connect(pThis->pSsl); + if (rcOssl > 0) + return VINF_SUCCESS; + + /** @todo better status codes. */ + if (BIO_should_retry(pThis->pBio)) + return VERR_TRY_AGAIN; + return VERR_NOT_SUPPORTED; +} + + +RTDECL(const char *) RTCrSslSessionGetVersion(RTCRSSLSESSION hSslSession) +{ + RTCRSSLSESSIONINT *pThis = hSslSession; + AssertPtrReturn(pThis, NULL); + AssertReturn(pThis->u32Magic == RTCRSSLSESSIONINT_MAGIC, NULL); + + return SSL_get_version(pThis->pSsl); +} + + +RTDECL(int) RTCrSslSessionGetCertIssuerNameAsString(RTCRSSLSESSION hSslSession, char *pszBuf, size_t cbBuf, size_t *pcbActual) +{ + RTCRSSLSESSIONINT *pThis = hSslSession; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRSSLSESSIONINT_MAGIC, VERR_INVALID_HANDLE); + AssertPtrNull(pszBuf); + AssertPtrNull(pcbActual); + if (pcbActual) + *pcbActual = 0; + + /* + * Get and format the certificate issuer name. + */ + int rc = VERR_NOT_AVAILABLE; + X509 *pCert = SSL_get_certificate(pThis->pSsl); + if (pCert) + { + X509_NAME *pIssuer = X509_get_issuer_name(pCert); + if (pIssuer) + { + char *pszSrc = X509_NAME_oneline(pIssuer, NULL, 0); + if (pszSrc) + { + /* + * Copy out the result and free it. + */ + size_t cbNeeded = strlen(pszSrc) + 1; + if (pcbActual) + *pcbActual = cbNeeded; + + if (pszBuf != NULL && cbBuf > 0) + { + if (cbBuf >= cbNeeded) + { + memcpy(pszBuf, pszSrc, cbNeeded); + rc = VINF_SUCCESS; + } + else + { + memcpy(pszBuf, pszSrc, cbBuf - 1); + pszBuf[cbBuf - 1] = '\0'; + rc = VERR_BUFFER_OVERFLOW; + } + } + else + rc = VERR_BUFFER_OVERFLOW; + OPENSSL_free(pszSrc); + } + } + } + return rc; +} + + +RTDECL(bool) RTCrSslSessionPending(RTCRSSLSESSION hSslSession) +{ + RTCRSSLSESSIONINT *pThis = hSslSession; + AssertPtrReturn(pThis, true); + AssertReturn(pThis->u32Magic == RTCRSSLSESSIONINT_MAGIC, true); + + return SSL_pending(pThis->pSsl) != 0; +} + + +RTDECL(ssize_t) RTCrSslSessionRead(RTCRSSLSESSION hSslSession, void *pvBuf, size_t cbToRead) +{ + RTCRSSLSESSIONINT *pThis = hSslSession; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRSSLSESSIONINT_MAGIC, VERR_INVALID_HANDLE); + + Assert((size_t)(int)cbToRead == cbToRead); + + int cbActual = SSL_read(pThis->pSsl, pvBuf, (int)cbToRead); + if (cbActual > 0) + return cbActual; + if (BIO_should_retry(pThis->pBio)) + return VERR_TRY_AGAIN; + return VERR_READ_ERROR; /** @todo better status codes. */ +} + + +RTDECL(ssize_t) RTCrSslSessionWrite(RTCRSSLSESSION hSslSession, void const *pvBuf, size_t cbToWrite) +{ + RTCRSSLSESSIONINT *pThis = hSslSession; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRSSLSESSIONINT_MAGIC, VERR_INVALID_HANDLE); + + Assert((size_t)(int)cbToWrite == cbToWrite); + Assert(cbToWrite != 0 /* undefined behavior if zero */); + + int cbActual = SSL_write(pThis->pSsl, pvBuf, (int)cbToWrite); + if (cbActual > 0) + return cbActual; + if (BIO_should_retry(pThis->pBio)) + return VERR_TRY_AGAIN; + return VERR_WRITE_ERROR; /** @todo better status codes. */ +} + +#endif /* IPRT_WITH_OPENSSL */ + diff --git a/src/VBox/Runtime/common/crypto/store-cert-add-basic.cpp b/src/VBox/Runtime/common/crypto/store-cert-add-basic.cpp new file mode 100644 index 00000000..ccbd239a --- /dev/null +++ b/src/VBox/Runtime/common/crypto/store-cert-add-basic.cpp @@ -0,0 +1,865 @@ +/* $Id: store-cert-add-basic.cpp $ */ +/** @file + * IPRT - Cryptographic (Certificate) Store, RTCrStoreCertAddFromDir. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/store.h> + +#include <iprt/assert.h> +#include <iprt/crypto/pem.h> +#include <iprt/dir.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/mem.h> +#include <iprt/path.h> +#include <iprt/sha.h> +#include <iprt/string.h> + +#include "x509-internal.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** BEGIN CERTIFICATE / END CERTIFICATE. */ +static RTCRPEMMARKERWORD const g_aWords_Certificate[] = +{ + { RT_STR_TUPLE("CERTIFICATE") } +}; + +/** BEGIN TRUSTED CERTIFICATE / END TRUSTED CERTIFICATE. */ +static RTCRPEMMARKERWORD const g_aWords_TrustedCertificate[] = +{ + { RT_STR_TUPLE("TRUSTED") }, + { RT_STR_TUPLE("CERTIFICATE") } +}; + +/** BEGIN X509 CERTIFICATE / END X509 CERTIFICATE. (old) */ +static RTCRPEMMARKERWORD const g_aWords_X509Certificate[] = +{ + { RT_STR_TUPLE("X509") }, + { RT_STR_TUPLE("CERTIFICATE") } +}; + +/** + * X509 Certificate markers. + * + * @remark See crypto/pem/pem.h in OpenSSL for a matching list. + */ +static RTCRPEMMARKER const g_aX509CertificateMarkers[3] = +{ + { g_aWords_Certificate, RT_ELEMENTS(g_aWords_Certificate) }, + { g_aWords_TrustedCertificate, RT_ELEMENTS(g_aWords_TrustedCertificate) }, + { g_aWords_X509Certificate, RT_ELEMENTS(g_aWords_X509Certificate) } +}; + + + +#ifdef RT_STRICT +/** + * Checks if we've found all the certificates already. + * + * @returns true if all found, false if not. + * @param afFound Indicator array. + * @param cWanted Number of wanted certificates. + */ +DECLINLINE(bool) rtCrStoreAllDone(bool const *afFound, size_t cWanted) +{ + while (cWanted-- > 0) + if (!afFound[cWanted]) + return false; + return true; +} +#endif /* RT_STRICT */ + + +/** + * Checks if the given certificate specs matches the given wanted poster. + * + * @returns true if match, false if not. + * @param pWanted The certificate wanted poster. + * @param cbEncoded The candidate certificate encoded size. + * @param paSha1 The candidate certificate SHA-1 fingerprint. + * @param paSha512 The candidate certificate SHA-512 fingerprint. + * @param pCert The decoded candidate certificate, optional. If not + * given the result will be uncertain. + */ +DECLINLINE(bool) rtCrStoreIsCertEqualToWanted(PCRTCRCERTWANTED pWanted, + size_t cbEncoded, + uint8_t const pabSha1[RTSHA1_HASH_SIZE], + uint8_t const pabSha512[RTSHA512_HASH_SIZE], + PCRTCRX509CERTIFICATE pCert) +{ + if ( pWanted->cbEncoded != cbEncoded + && pWanted->cbEncoded != 0) + return false; + + if ( pWanted->fSha1Fingerprint + && memcmp(pWanted->abSha1, pabSha1, RTSHA1_HASH_SIZE) != 0) + return false; + + if ( pWanted->fSha512Fingerprint + && memcmp(pWanted->abSha512, pabSha512, RTSHA512_HASH_SIZE) != 0) + return false; + + if ( pWanted->pszSubject + && pCert + && !RTCrX509Name_MatchWithString(&pCert->TbsCertificate.Subject, pWanted->pszSubject)) + return false; + + return true; +} + + +/** + * Checks if a certificate is wanted. + * + * @returns true if match, false if not. + * @param paWanted The certificate wanted posters. + * @param cWanted The number of wanted posters. + * @param apfFound Found initicators running paralell to @a paWanted. + * @param cbEncoded The candidate certificate encoded size. + * @param paSha1 The candidate certificate SHA-1 fingerprint. + * @param paSha512 The candidate certificate SHA-512 fingerprint. + * @param pCert The decoded candidate certificate, optional. If not + * given the result will be uncertain. + */ +DECLINLINE(bool) rtCrStoreIsCertWanted(PCRTCRCERTWANTED paWanted, size_t cWanted, bool const *pafFound, size_t cbEncoded, + uint8_t const pabSha1[RTSHA1_HASH_SIZE], uint8_t const pabSha512[RTSHA512_HASH_SIZE], + PCRTCRX509CERTIFICATE pCert) +{ + for (size_t iCert = 0; iCert < cWanted; iCert++) + if (!pafFound[iCert]) + if (rtCrStoreIsCertEqualToWanted(&paWanted[iCert], cbEncoded, pabSha1, pabSha512, pCert)) + return true; + return false; +} + + +/** + * Marks a certificate as found after it has been added to the store. + * + * May actually mark several certificates as found if there are duplicates or + * ambiguities in the wanted list. + * + * @returns true if all have been found, false if more to search for. + * + * @param apfFound Found initicators running paralell to @a paWanted. + * This is what this function updates. + * @param paWanted The certificate wanted posters. + * @param cWanted The number of wanted posters. + * @param cbEncoded The candidate certificate encoded size. + * @param paSha1 The candidate certificate SHA-1 fingerprint. + * @param paSha512 The candidate certificate SHA-512 fingerprint. + * @param pCert The decoded candidate certificate, optional. If not + * given the result will be uncertain. + */ +static bool rtCrStoreMarkCertFound(bool *pafFound, PCRTCRCERTWANTED paWanted, size_t cWanted, size_t cbEncoded, + uint8_t const pabSha1[RTSHA1_HASH_SIZE], uint8_t const pabSha512[RTSHA512_HASH_SIZE], + PCRTCRX509CERTIFICATE pCert) +{ + size_t cFound = 0; + for (size_t iCert = 0; iCert < cWanted; iCert++) + if (pafFound[iCert]) + cFound++; + else if (rtCrStoreIsCertEqualToWanted(&paWanted[iCert], cbEncoded, pabSha1, pabSha512, pCert)) + { + pafFound[iCert] = true; + cFound++; + } + return cFound == cWanted; +} + + +RTDECL(int) RTCrStoreCertAddFromStore(RTCRSTORE hStore, uint32_t fFlags, RTCRSTORE hStoreSrc) +{ + /* + * Validate input. + */ + AssertReturn(!(fFlags & ~(RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR)), VERR_INVALID_FLAGS); + + /* + * Enumerate all the certificates in the source store, adding them to the destination. + */ + RTCRSTORECERTSEARCH Search; + int rc = RTCrStoreCertFindAll(hStoreSrc, &Search); + if (RT_SUCCESS(rc)) + { + PCRTCRCERTCTX pCertCtx; + while ((pCertCtx = RTCrStoreCertSearchNext(hStoreSrc, &Search)) != NULL) + { + int rc2 = RTCrStoreCertAddEncoded(hStore, pCertCtx->fFlags | (fFlags & RTCRCERTCTX_F_ADD_IF_NOT_FOUND), + pCertCtx->pabEncoded, pCertCtx->cbEncoded, NULL); + if (RT_FAILURE(rc2)) + { + rc = rc2; + if (!(fFlags & RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR)) + break; + } + RTCrCertCtxRelease(pCertCtx); + } + + int rc2 = RTCrStoreCertSearchDestroy(hStoreSrc, &Search); AssertRC(rc2); + } + return rc; +} +RT_EXPORT_SYMBOL(RTCrStoreCertAddFromStore); + + +RTDECL(int) RTCrStoreCertAddWantedFromStore(RTCRSTORE hStore, uint32_t fFlags, RTCRSTORE hSrcStore, + PCRTCRCERTWANTED paWanted, size_t cWanted, bool *pafFound) +{ + /* + * Validate input a little. + */ + AssertReturn(!(fFlags & ~(RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR)), VERR_INVALID_FLAGS); + fFlags |= RTCRCERTCTX_F_ADD_IF_NOT_FOUND; /* forced */ + + AssertReturn(cWanted, VERR_NOT_FOUND); + for (uint32_t i = 0; i < cWanted; i++) + { + AssertReturn(!paWanted[i].pszSubject || *paWanted[i].pszSubject, VERR_INVALID_PARAMETER); + AssertReturn( paWanted[i].pszSubject + || paWanted[i].fSha1Fingerprint + || paWanted[i].fSha512Fingerprint, + VERR_INVALID_PARAMETER); + } + + /* + * Make sure we've got a result array. + */ + bool *pafFoundFree = NULL; + if (!pafFound) + { + pafFound = pafFoundFree = (bool *)RTMemTmpAllocZ(sizeof(bool) * cWanted); + AssertReturn(pafFound, VERR_NO_TMP_MEMORY); + } + + /* + * Enumerate the store entries. + */ + RTCRSTORECERTSEARCH Search; + int rc = RTCrStoreCertFindAll(hSrcStore, &Search); + if (RT_SUCCESS(rc)) + { + rc = VWRN_NOT_FOUND; + PCRTCRCERTCTX pCertCtx; + while ((pCertCtx = RTCrStoreCertSearchNext(hSrcStore, &Search)) != NULL) + { + if ( (pCertCtx->fFlags & RTCRCERTCTX_F_ENC_MASK) == RTCRCERTCTX_F_ENC_X509_DER + && pCertCtx->cbEncoded > 0 + && pCertCtx->pCert) + { + /* + * If the certificate is wanted, try add it to the store. + */ + uint8_t abSha1[RTSHA1_HASH_SIZE]; + RTSha1(pCertCtx->pabEncoded, pCertCtx->cbEncoded, abSha1); + uint8_t abSha512[RTSHA512_HASH_SIZE]; + RTSha512(pCertCtx->pabEncoded, pCertCtx->cbEncoded, abSha512); + if (rtCrStoreIsCertWanted(paWanted, cWanted, pafFound, pCertCtx->cbEncoded, abSha1, abSha512, pCertCtx->pCert)) + { + int rc2 = RTCrStoreCertAddEncoded(hStore, + RTCRCERTCTX_F_ENC_X509_DER | (fFlags & RTCRCERTCTX_F_ADD_IF_NOT_FOUND), + pCertCtx->pabEncoded, pCertCtx->cbEncoded, NULL /*pErrInfo*/); + if (RT_SUCCESS(rc2)) + { + /* + * Mark it as found, stop if we've found all. + */ + if (rtCrStoreMarkCertFound(pafFound, paWanted, cWanted, + pCertCtx->cbEncoded, abSha1, abSha512, pCertCtx->pCert)) + { + if (RT_SUCCESS(rc)) + rc = VINF_SUCCESS; + RTCrCertCtxRelease(pCertCtx); + break; + } + } + else + { + /* + * Some error adding the certificate. Since it cannot be anything with + * the encoding, it must be something with the store or resources, so + * always return the error status. + */ + rc = rc2; + if (!(fFlags & RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR)) + { + RTCrCertCtxRelease(pCertCtx); + break; + } + } + } + } + RTCrCertCtxRelease(pCertCtx); + } + int rc2 = RTCrStoreCertSearchDestroy(hSrcStore, &Search); + AssertRC(rc2); + } + + if (pafFoundFree) + RTMemTmpFree(pafFoundFree); + return rc; +} +RT_EXPORT_SYMBOL(RTCrStoreCertAddWantedFromStore); + + +RTDECL(int) RTCrStoreCertCheckWanted(RTCRSTORE hStore, PCRTCRCERTWANTED paWanted, size_t cWanted, bool *pafFound) +{ + /* + * Validate input a little. + */ + AssertReturn(cWanted, VERR_NOT_FOUND); + for (uint32_t i = 0; i < cWanted; i++) + { + AssertReturn(!paWanted[i].pszSubject || *paWanted[i].pszSubject, VERR_INVALID_PARAMETER); + AssertReturn( paWanted[i].pszSubject + || paWanted[i].fSha1Fingerprint + || paWanted[i].fSha512Fingerprint, + VERR_INVALID_PARAMETER); + } + AssertPtrReturn(pafFound, VERR_INVALID_POINTER); + + /* + * Clear the found array. + */ + for (uint32_t iCert = 0; iCert < cWanted; iCert++) + pafFound[iCert] = false; + + /* + * Enumerate the store entries. + */ + RTCRSTORECERTSEARCH Search; + int rc = RTCrStoreCertFindAll(hStore, &Search); + if (RT_SUCCESS(rc)) + { + rc = VWRN_NOT_FOUND; + PCRTCRCERTCTX pCertCtx; + while ((pCertCtx = RTCrStoreCertSearchNext(hStore, &Search)) != NULL) + { + if ( (pCertCtx->fFlags & RTCRCERTCTX_F_ENC_MASK) == RTCRCERTCTX_F_ENC_X509_DER + && pCertCtx->cbEncoded > 0 + && pCertCtx->pCert) + { + /* + * Hash it and check if it's wanted. Stop when we've found all. + */ + uint8_t abSha1[RTSHA1_HASH_SIZE]; + RTSha1(pCertCtx->pabEncoded, pCertCtx->cbEncoded, abSha1); + uint8_t abSha512[RTSHA512_HASH_SIZE]; + RTSha512(pCertCtx->pabEncoded, pCertCtx->cbEncoded, abSha512); + if (rtCrStoreMarkCertFound(pafFound, paWanted, cWanted, pCertCtx->cbEncoded, abSha1, abSha512, pCertCtx->pCert)) + { + rc = VINF_SUCCESS; + RTCrCertCtxRelease(pCertCtx); + break; + } + } + RTCrCertCtxRelease(pCertCtx); + } + int rc2 = RTCrStoreCertSearchDestroy(hStore, &Search); + AssertRC(rc2); + } + + return rc; +} +RT_EXPORT_SYMBOL(RTCrStoreCertAddWantedFromStore); + + +RTDECL(int) RTCrStoreCertAddFromFile(RTCRSTORE hStore, uint32_t fFlags, const char *pszFilename, PRTERRINFO pErrInfo) +{ + AssertReturn(!(fFlags & ~(RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR)), VERR_INVALID_FLAGS); + + size_t cbContent; + void *pvContent; + int rc = RTFileReadAllEx(pszFilename, 0, 64U*_1M, RTFILE_RDALL_O_DENY_WRITE, &pvContent, &cbContent); + if (RT_SUCCESS(rc)) + { + /* + * Is it a java key store file? + */ + if ( cbContent > 32 + && ((uint32_t const *)pvContent)[0] == RT_H2BE_U32_C(UINT32_C(0xfeedfeed)) /* magic */ + && ((uint32_t const *)pvContent)[1] == RT_H2BE_U32_C(UINT32_C(0x00000002)) /* version */ ) + rc = RTCrStoreCertAddFromJavaKeyStoreInMem(hStore, fFlags, pvContent, cbContent, pszFilename, pErrInfo); + /* + * No assume PEM or DER encoded binary certificate. + */ + else if (cbContent) + { + PCRTCRPEMSECTION pSectionHead; + rc = RTCrPemParseContent(pvContent, cbContent, + (fFlags & RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR) + ? RTCRPEMREADFILE_F_CONTINUE_ON_ENCODING_ERROR : 0, + g_aX509CertificateMarkers, RT_ELEMENTS(g_aX509CertificateMarkers), + &pSectionHead, pErrInfo); + if (RT_SUCCESS(rc)) + { + PCRTCRPEMSECTION pCurSec = pSectionHead; + while (pCurSec) + { + int rc2 = RTCrStoreCertAddEncoded(hStore, + RTCRCERTCTX_F_ENC_X509_DER | (fFlags & RTCRCERTCTX_F_ADD_IF_NOT_FOUND), + pCurSec->pbData, pCurSec->cbData, + !RTErrInfoIsSet(pErrInfo) ? pErrInfo : NULL); + if (RT_FAILURE(rc2) && RT_SUCCESS(rc)) + { + rc = rc2; + if (!(fFlags & RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR)) + break; + } + pCurSec = pCurSec->pNext; + } + + RTCrPemFreeSections(pSectionHead); + } + } + else /* Will happen if proxy not set / no connection available. */ + rc = RTErrInfoSetF(pErrInfo, VERR_EOF, "Certificate '%s' is empty", pszFilename); + RTFileReadAllFree(pvContent, cbContent); + } + else + rc = RTErrInfoSetF(pErrInfo, rc, "RTFileReadAllEx failed with %Rrc on '%s'", rc, pszFilename); + return rc; +} +RT_EXPORT_SYMBOL(RTCrStoreCertAddFromFile); + + +RTDECL(int) RTCrStoreCertAddWantedFromFile(RTCRSTORE hStore, uint32_t fFlags, const char *pszFilename, + PCRTCRCERTWANTED paWanted, size_t cWanted, bool *pafFound, PRTERRINFO pErrInfo) +{ + /* + * Validate input a little. + */ + AssertReturn(!(fFlags & ~(RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR)), VERR_INVALID_FLAGS); + fFlags |= RTCRCERTCTX_F_ADD_IF_NOT_FOUND; /* forced */ + + AssertReturn(cWanted, VERR_NOT_FOUND); + for (uint32_t i = 0; i < cWanted; i++) + { + AssertReturn(!paWanted[i].pszSubject || *paWanted[i].pszSubject, VERR_INVALID_PARAMETER); + AssertReturn( paWanted[i].pszSubject + || paWanted[i].fSha1Fingerprint + || paWanted[i].fSha512Fingerprint, + VERR_INVALID_PARAMETER); + } + + /* + * Make sure we've got a result array. + */ + bool *pafFoundFree = NULL; + if (!pafFound) + { + pafFound = pafFoundFree = (bool *)RTMemTmpAllocZ(sizeof(bool) * cWanted); + AssertReturn(pafFound, VERR_NO_TMP_MEMORY); + } + + size_t cbContent; + void *pvContent; + int rc = RTFileReadAllEx(pszFilename, 0, 64U*_1M, RTFILE_RDALL_O_DENY_WRITE, &pvContent, &cbContent); + if (RT_SUCCESS(rc)) + { + /* + * Is it a java key store file? If so, load it into a tmp store + * which we can search. Don't want to duplicate the JKS reader code. + */ + if ( cbContent > 32 + && ((uint32_t const *)pvContent)[0] == RT_H2BE_U32_C(UINT32_C(0xfeedfeed)) /* magic */ + && ((uint32_t const *)pvContent)[1] == RT_H2BE_U32_C(UINT32_C(0x00000002)) /* version */ ) + { + RTCRSTORE hTmpStore; + rc = RTCrStoreCreateInMem(&hTmpStore, 64); + if (RT_SUCCESS(rc)) + { + rc = RTCrStoreCertAddFromJavaKeyStoreInMem(hStore, fFlags, pvContent, cbContent, pszFilename, pErrInfo); + if (RT_SUCCESS(rc)) + rc = RTCrStoreCertAddWantedFromStore(hStore, fFlags, hTmpStore, paWanted, cWanted, pafFound); + RTCrStoreRelease(hTmpStore); + } + else + rc = RTErrInfoSet(pErrInfo, rc, "Error creating temporary crypto store"); + } + /* + * No assume PEM or DER encoded binary certificate. Inspect them one by one. + */ + else if (cbContent) + { + PCRTCRPEMSECTION pSectionHead; + rc = RTCrPemParseContent(pvContent, cbContent, + (fFlags & RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR) + ? RTCRPEMREADFILE_F_CONTINUE_ON_ENCODING_ERROR : 0, + g_aX509CertificateMarkers, RT_ELEMENTS(g_aX509CertificateMarkers), + &pSectionHead, pErrInfo); + if (RT_SUCCESS(rc)) + { + rc = VWRN_NOT_FOUND; + for (PCRTCRPEMSECTION pCurSec = pSectionHead; pCurSec; pCurSec = pCurSec->pNext) + { + if (!pCurSec->cbData) + continue; + + /* + * See if this is a binary blob we might be interested in. + */ + uint8_t abSha1[RTSHA1_HASH_SIZE]; + RTSha1(pCurSec->pbData, pCurSec->cbData, abSha1); + uint8_t abSha512[RTSHA512_HASH_SIZE]; + RTSha512(pCurSec->pbData, pCurSec->cbData, abSha512); + if (!rtCrStoreIsCertWanted(paWanted, cWanted, pafFound, pCurSec->cbData, abSha1, abSha512, NULL)) + continue; + + /* + * Decode the certificate so we can match the subject string. + */ + RTASN1CURSORPRIMARY Cursor; + RTAsn1CursorInitPrimary(&Cursor, pCurSec->pbData, (uint32_t)pCurSec->cbData, + !RTErrInfoIsSet(pErrInfo) ? pErrInfo : NULL, + &g_RTAsn1DefaultAllocator, RTASN1CURSOR_FLAGS_DER, "InMem"); + RTCRX509CERTIFICATE X509Cert; + int rc2 = RTCrX509Certificate_DecodeAsn1(&Cursor.Cursor, 0, &X509Cert, "Cert"); + if (RT_SUCCESS(rc2)) + { + rc2 = RTCrX509Certificate_CheckSanity(&X509Cert, 0, !RTErrInfoIsSet(pErrInfo) ? pErrInfo : NULL, "Cert"); + if (RT_SUCCESS(rc2)) + { + if (rtCrStoreIsCertWanted(paWanted, cWanted, pafFound, pCurSec->cbData, abSha1, abSha512, &X509Cert)) + { + /* + * The certificate is wanted, now add it to the store. + */ + rc2 = RTCrStoreCertAddEncoded(hStore, + RTCRCERTCTX_F_ENC_X509_DER + | (fFlags & RTCRCERTCTX_F_ADD_IF_NOT_FOUND), + pCurSec->pbData, pCurSec->cbData, + !RTErrInfoIsSet(pErrInfo) ? pErrInfo : NULL); + if (RT_SUCCESS(rc2)) + { + /* + * Mark it as found, stop if we've found all. + */ + if (rtCrStoreMarkCertFound(pafFound, paWanted, cWanted, + pCurSec->cbData, abSha1, abSha512, &X509Cert)) + { + RTAsn1VtDelete(&X509Cert.SeqCore.Asn1Core); + rc = VINF_SUCCESS; + break; + } + } + } + } + else + Assert(!pErrInfo || RTErrInfoIsSet(pErrInfo)); + RTAsn1VtDelete(&X509Cert.SeqCore.Asn1Core); + } + else if (!RTErrInfoIsSet(pErrInfo)) + RTErrInfoSetF(pErrInfo, rc2, "RTCrX509Certificate_DecodeAsn1 failed"); + + /* + * Stop on error, if requested. Otherwise, let pErrInfo keep it. + */ + if (RT_FAILURE(rc2) && !(fFlags & RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR)) + { + rc = rc2; + break; + } + } /* For each PEM section. */ + + RTCrPemFreeSections(pSectionHead); + } + } + else /* Will happen if proxy not set / no connection available. */ + rc = RTErrInfoSetF(pErrInfo, VERR_EOF, "Certificate '%s' is empty", pszFilename); + RTFileReadAllFree(pvContent, cbContent); + } + else + rc = RTErrInfoSetF(pErrInfo, rc, "RTFileReadAllEx failed with %Rrc on '%s'", rc, pszFilename); + + if (pafFoundFree) + RTMemTmpFree(pafFoundFree); + return rc; +} +RT_EXPORT_SYMBOL(RTCrStoreCertAddWantedFromFile); + + +/** + * Checks if the directory entry matches the specified suffixes. + * + * @returns true on match, false on mismatch. + * @param pDirEntry The directory to check. + * @param paSuffixes The array of suffixes to match against. + * @param cSuffixes The number of suffixes in the array. + */ +DECLINLINE(bool) rtCrStoreIsSuffixMatch(PCRTDIRENTRY pDirEntry, PCRTSTRTUPLE paSuffixes, size_t cSuffixes) +{ + if (cSuffixes == 0) + return true; + + size_t const cchName = pDirEntry->cbName; + size_t i = cSuffixes; + while (i-- > 0) + if ( cchName > paSuffixes[i].cch + && memcmp(&pDirEntry->szName[cchName - paSuffixes[i].cch], paSuffixes[i].psz, paSuffixes[i].cch) == 0) + return true; + + return false; +} + + +RTDECL(int) RTCrStoreCertAddFromDir(RTCRSTORE hStore, uint32_t fFlags, const char *pszDir, + PCRTSTRTUPLE paSuffixes, size_t cSuffixes, PRTERRINFO pErrInfo) +{ + /* + * Validate input. + */ + AssertReturn(!(fFlags & ~(RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR)), VERR_INVALID_FLAGS); + size_t i = cSuffixes; + while (i-- > 0) + { + Assert(paSuffixes[i].cch > 0); + Assert(strlen(paSuffixes[i].psz) == paSuffixes[i].cch); + } + + /* + * Prepare for constructing path to the files in the directory, so that we + * can open them. + */ + char szPath[RTPATH_MAX]; + int rc = RTStrCopy(szPath, sizeof(szPath), pszDir); + if (RT_SUCCESS(rc)) + { + size_t cchPath = RTPathEnsureTrailingSeparator(szPath, sizeof(szPath)); + if (cchPath > 0) + { + size_t const cbMaxFilename = sizeof(szPath) - cchPath; + + /* + * Enumerate the directory. + */ + RTDIR hDir; + rc = RTDirOpen(&hDir, pszDir); + if (RT_SUCCESS(rc)) + { + for (;;) + { + /* Read the next entry. */ + union + { + RTDIRENTRY DirEntry; + uint8_t abPadding[RTPATH_MAX + sizeof(RTDIRENTRY)]; + } u; + size_t cbBuf = sizeof(u); + int rc2 = RTDirRead(hDir, &u.DirEntry, &cbBuf); + if (RT_SUCCESS(rc2)) + { + if ( ( u.DirEntry.enmType == RTDIRENTRYTYPE_FILE + || u.DirEntry.enmType == RTDIRENTRYTYPE_SYMLINK + || ( u.DirEntry.enmType == RTDIRENTRYTYPE_UNKNOWN + && !RTDirEntryIsStdDotLink(&u.DirEntry)) ) + && rtCrStoreIsSuffixMatch(&u.DirEntry, paSuffixes, cSuffixes) ) + { + if (u.DirEntry.cbName < cbMaxFilename) + { + memcpy(&szPath[cchPath], u.DirEntry.szName, u.DirEntry.cbName + 1); + rc2 = RTDirQueryUnknownType(szPath, true /*fFollowSymlinks*/, &u.DirEntry.enmType); + if ( RT_SUCCESS(rc2) + && u.DirEntry.enmType == RTDIRENTRYTYPE_FILE) + { + /* + * Add it. + */ + rc2 = RTCrStoreCertAddFromFile(hStore, fFlags, szPath, pErrInfo); + if (RT_FAILURE(rc2)) + { + rc = rc2; + if (!(fFlags & RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR)) + break; + } + } + } + else + { + rc = RTErrInfoAddF(pErrInfo, VERR_FILENAME_TOO_LONG, + " Too long filename (%u bytes)", u.DirEntry.cbName); + if (!(fFlags & RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR)) + break; + } + } + } + else + { + if (rc2 != VERR_NO_MORE_FILES) + rc = RTErrInfoAddF(pErrInfo, rc2, " RTDirRead failed: %Rrc", rc2); + break; + } + } + + RTDirClose(hDir); + } + else + rc = RTErrInfoAddF(pErrInfo, rc, " RTDirOpen('%s'): %Rrc", pszDir, rc); + } + else + rc = VERR_FILENAME_TOO_LONG; + } + return rc; +} +RT_EXPORT_SYMBOL(RTCrStoreCertAddFromDir); + + +RTDECL(int) RTCrStoreCertAddWantedFromDir(RTCRSTORE hStore, uint32_t fFlags, + const char *pszDir, PCRTSTRTUPLE paSuffixes, size_t cSuffixes, + PCRTCRCERTWANTED paWanted, size_t cWanted, bool *pafFound, PRTERRINFO pErrInfo) +{ + /* + * Validate input a little. + */ + AssertReturn(*pszDir, VERR_PATH_ZERO_LENGTH); + AssertReturn(!(fFlags & ~(RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR)), VERR_INVALID_FLAGS); + fFlags |= RTCRCERTCTX_F_ADD_IF_NOT_FOUND; /* forced */ + + AssertReturn(cWanted, VERR_NOT_FOUND); + for (uint32_t i = 0; i < cWanted; i++) + { + AssertReturn(!paWanted[i].pszSubject || *paWanted[i].pszSubject, VERR_INVALID_PARAMETER); + AssertReturn( paWanted[i].pszSubject + || paWanted[i].fSha1Fingerprint + || paWanted[i].fSha512Fingerprint, + VERR_INVALID_PARAMETER); + } + + /* + * Prepare for constructing path to the files in the directory, so that we + * can open them. + */ + char szPath[RTPATH_MAX]; + int rc = RTStrCopy(szPath, sizeof(szPath), pszDir); + if (RT_SUCCESS(rc)) + { + size_t cchPath = RTPathEnsureTrailingSeparator(szPath, sizeof(szPath)); + if (cchPath > 0) + { + size_t const cbMaxFilename = sizeof(szPath) - cchPath; + + /* + * Enumerate the directory. + */ + RTDIR hDir; + rc = RTDirOpen(&hDir, pszDir); + if (RT_SUCCESS(rc)) + { + rc = VWRN_NOT_FOUND; + for (;;) + { + /* Read the next entry. */ + union + { + RTDIRENTRY DirEntry; + uint8_t abPadding[RTPATH_MAX + sizeof(RTDIRENTRY)]; + } u; + size_t cbEntry = sizeof(u); + int rc2 = RTDirRead(hDir, &u.DirEntry, &cbEntry); + if (RT_SUCCESS(rc2)) + { + if ( ( u.DirEntry.enmType == RTDIRENTRYTYPE_FILE + || u.DirEntry.enmType == RTDIRENTRYTYPE_SYMLINK + || ( u.DirEntry.enmType == RTDIRENTRYTYPE_UNKNOWN + && !RTDirEntryIsStdDotLink(&u.DirEntry)) ) + && rtCrStoreIsSuffixMatch(&u.DirEntry, paSuffixes, cSuffixes) ) + { + if (u.DirEntry.cbName < cbMaxFilename) + { + memcpy(&szPath[cchPath], u.DirEntry.szName, u.DirEntry.cbName); + szPath[cchPath + u.DirEntry.cbName] = '\0'; + if (u.DirEntry.enmType != RTDIRENTRYTYPE_FILE) + RTDirQueryUnknownType(szPath, true /*fFollowSymlinks*/, &u.DirEntry.enmType); + if (u.DirEntry.enmType == RTDIRENTRYTYPE_FILE) + { + rc2 = RTCrStoreCertAddWantedFromFile(hStore, fFlags, szPath, + paWanted, cWanted, pafFound, pErrInfo); + if (rc2 == VINF_SUCCESS) + { + Assert(rtCrStoreAllDone(pafFound, cWanted)); + if (RT_SUCCESS(rc)) + rc = VINF_SUCCESS; + break; + } + if (RT_FAILURE(rc2) && !(fFlags & RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR)) + { + rc = rc2; + break; + } + } + } + else + { + /* + * pErrInfo keeps the status code unless it's fatal. + */ + RTErrInfoAddF(pErrInfo, VERR_FILENAME_TOO_LONG, + " Too long filename (%u bytes)", u.DirEntry.cbName); + if (!(fFlags & RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR)) + { + rc = VERR_FILENAME_TOO_LONG; + break; + } + } + } + } + else + { + if (rc2 != VERR_NO_MORE_FILES) + { + RTErrInfoAddF(pErrInfo, rc2, "RTDirRead failed: %Rrc", rc2); + if (!(fFlags & RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR)) + rc = rc2; + } + break; + } + } + RTDirClose(hDir); + } + } + else + rc = VERR_FILENAME_TOO_LONG; + } + return rc; +} +RT_EXPORT_SYMBOL(RTCrStoreCertAddWantedFromDir); + diff --git a/src/VBox/Runtime/common/crypto/store-inmem.cpp b/src/VBox/Runtime/common/crypto/store-inmem.cpp new file mode 100644 index 00000000..916b3253 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/store-inmem.cpp @@ -0,0 +1,466 @@ +/* $Id: store-inmem.cpp $ */ +/** @file + * IPRT - In Memory Cryptographic Certificate Store. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/store.h> + +#include <iprt/asm.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/string.h> + +#include "store-internal.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * A certificate entry in the in-memory store. + */ +typedef struct RTCRSTOREINMEMCERT +{ + /** The core certificate context. */ + RTCRCERTCTXINT Core; + /** Internal copy of the flag (paranoia). */ + uint32_t fFlags; + /** Decoded data. */ + union + { + /** ASN.1 core structure for generic access. */ + RTASN1CORE Asn1Core; + /** The decoded X.509 certificate (RTCRCERTCTX_F_ENC_X509_DER). */ + RTCRX509CERTIFICATE X509Cert; + /** The decoded trust anchor info (RTCRCERTCTX_F_ENC_TAF_DER). */ + RTCRTAFTRUSTANCHORINFO TaInfo; + } u; + /** Pointer to the store if still in it (no reference). */ + struct RTCRSTOREINMEM *pStore; + /** The DER encoding of the certificate. */ + uint8_t abEncoded[1]; +} RTCRSTOREINMEMCERT; +AssertCompileMembersAtSameOffset(RTCRSTOREINMEMCERT, u.X509Cert.SeqCore.Asn1Core, RTCRSTOREINMEMCERT, u.Asn1Core); +AssertCompileMembersAtSameOffset(RTCRSTOREINMEMCERT, u.TaInfo.SeqCore.Asn1Core, RTCRSTOREINMEMCERT, u.Asn1Core); +/** Pointer to an in-memory store certificate entry. */ +typedef RTCRSTOREINMEMCERT *PRTCRSTOREINMEMCERT; + + +/** + * The per instance data of a in-memory crypto store. + * + * Currently we ASSUME we don't need serialization. Add that when needed! + */ +typedef struct RTCRSTOREINMEM +{ + /** The number of certificates. */ + uint32_t cCerts; + /** The max number of certificates papCerts can store before growing it. */ + uint32_t cCertsAlloc; + /** Array of certificates. */ + PRTCRSTOREINMEMCERT *papCerts; + + /** Parent store. */ + RTCRSTORE hParentStore; + /** The parent store callback table. */ + PCRTCRSTOREPROVIDER pParentProvider; + /** The parent store provider callback argument. */ + void *pvParentProvider; +} RTCRSTOREINMEM; +/** Pointer to an in-memory crypto store. */ +typedef RTCRSTOREINMEM *PRTCRSTOREINMEM; + + + + +static DECLCALLBACK(void) rtCrStoreInMemCertEntry_Dtor(PRTCRCERTCTXINT pCertCtx) +{ + PRTCRSTOREINMEMCERT pEntry = (PRTCRSTOREINMEMCERT)pCertCtx; + AssertRelease(!pEntry->pStore); + + pEntry->Core.pfnDtor = NULL; + RTAsn1VtDelete(&pEntry->u.Asn1Core); + RTMemFree(pEntry); +} + + +/** + * Internal method for allocating and initalizing a certificate entry in the + * in-memory store. + * + * @returns IPRT status code. + * @param pThis The in-memory store instance. + * @param fEnc RTCRCERTCTX_F_ENC_X509_DER or RTCRCERTCTX_F_ENC_TAF_DER. + * @param pbSrc The DER encoded X.509 certificate to add. + * @param cbSrc The size of the encoded certificate. + * @param pErrInfo Where to return extended error info. Optional. + * @param ppEntry Where to return the pointer to the new entry. + */ +static int rtCrStoreInMemCreateCertEntry(PRTCRSTOREINMEM pThis, uint32_t fEnc, uint8_t const *pbSrc, uint32_t cbSrc, + PRTERRINFO pErrInfo, PRTCRSTOREINMEMCERT *ppEntry) +{ + int rc; + PRTCRSTOREINMEMCERT pEntry = (PRTCRSTOREINMEMCERT)RTMemAllocZ(RT_UOFFSETOF_DYN(RTCRSTOREINMEMCERT, abEncoded[cbSrc])); + if (pEntry) + { + memcpy(pEntry->abEncoded, pbSrc, cbSrc); + pEntry->Core.u32Magic = RTCRCERTCTXINT_MAGIC; + pEntry->Core.cRefs = 1; + pEntry->Core.pfnDtor = rtCrStoreInMemCertEntry_Dtor; + pEntry->Core.Public.fFlags = fEnc; + pEntry->Core.Public.cbEncoded = cbSrc; + pEntry->Core.Public.pabEncoded = &pEntry->abEncoded[0]; + if (fEnc == RTCRCERTCTX_F_ENC_X509_DER) + { + pEntry->Core.Public.pCert = &pEntry->u.X509Cert; + pEntry->Core.Public.pTaInfo = NULL; + } + else + { + pEntry->Core.Public.pCert = NULL; + pEntry->Core.Public.pTaInfo = &pEntry->u.TaInfo; + } + pEntry->pStore = pThis; + + RTASN1CURSORPRIMARY Cursor; + RTAsn1CursorInitPrimary(&Cursor, &pEntry->abEncoded[0], cbSrc, pErrInfo, &g_RTAsn1DefaultAllocator, + RTASN1CURSOR_FLAGS_DER, "InMem"); + if (fEnc == RTCRCERTCTX_F_ENC_X509_DER) + rc = RTCrX509Certificate_DecodeAsn1(&Cursor.Cursor, 0, &pEntry->u.X509Cert, "Cert"); + else + rc = RTCrTafTrustAnchorInfo_DecodeAsn1(&Cursor.Cursor, 0, &pEntry->u.TaInfo, "TaInfo"); + if (RT_SUCCESS(rc)) + { + if (fEnc == RTCRCERTCTX_F_ENC_X509_DER) + rc = RTCrX509Certificate_CheckSanity(&pEntry->u.X509Cert, 0, pErrInfo, "Cert"); + else + rc = RTCrTafTrustAnchorInfo_CheckSanity(&pEntry->u.TaInfo, 0, pErrInfo, "TaInfo"); + if (RT_SUCCESS(rc)) + { + *ppEntry = pEntry; + return VINF_SUCCESS; + } + + RTAsn1VtDelete(&pEntry->u.Asn1Core); + } + RTMemFree(pEntry); + } + else + rc = VERR_NO_MEMORY; + return rc; +} + + +/** + * Grows the certificate pointer array to at least @a cMin entries. + * + * @returns IPRT status code. + * @param pThis The in-memory store instance. + * @param cMin The new minimum store size. + */ +static int rtCrStoreInMemGrow(PRTCRSTOREINMEM pThis, uint32_t cMin) +{ + AssertReturn(cMin <= _1M, VERR_OUT_OF_RANGE); + AssertReturn(cMin > pThis->cCertsAlloc, VERR_INTERNAL_ERROR_3); + + if (cMin < 64) + cMin = RT_ALIGN_32(cMin, 8); + else + cMin = RT_ALIGN_32(cMin, 32); + + void *pv = RTMemRealloc(pThis->papCerts, cMin * sizeof(pThis->papCerts[0])); + if (pv) + { + pThis->papCerts = (PRTCRSTOREINMEMCERT *)pv; + for (uint32_t i = pThis->cCertsAlloc; i < cMin; i++) + pThis->papCerts[i] = NULL; + pThis->cCertsAlloc = cMin; + return VINF_SUCCESS; + } + return VERR_NO_MEMORY; +} + + + +/** @interface_method_impl{RTCRSTOREPROVIDER,pfnDestroyStore} */ +static DECLCALLBACK(void) rtCrStoreInMem_DestroyStore(void *pvProvider) +{ + PRTCRSTOREINMEM pThis = (PRTCRSTOREINMEM)pvProvider; + + while (pThis->cCerts > 0) + { + uint32_t i = --pThis->cCerts; + PRTCRSTOREINMEMCERT pEntry = pThis->papCerts[i]; + pThis->papCerts[i] = NULL; + AssertPtr(pEntry); + + pEntry->pStore = NULL; + RTCrCertCtxRelease(&pEntry->Core.Public); + } + + RTMemFree(pThis->papCerts); + pThis->papCerts = NULL; + + if (pThis->hParentStore != NIL_RTCRSTORE) + { + RTCrStoreRelease(pThis->hParentStore); + pThis->hParentStore = NIL_RTCRSTORE; + } + + RTMemFree(pThis); +} + + +/** @interface_method_impl{RTCRSTOREPROVIDER,pfnCertCtxQueryPrivateKey} */ +static DECLCALLBACK(int) rtCrStoreInMem_CertCtxQueryPrivateKey(void *pvProvider, PRTCRCERTCTXINT pCertCtx, + uint8_t *pbKey, size_t cbKey, size_t *pcbKeyRet) +{ + RT_NOREF_PV(pvProvider); RT_NOREF_PV(pCertCtx); RT_NOREF_PV(pbKey); RT_NOREF_PV(cbKey); RT_NOREF_PV(pcbKeyRet); + //PRTCRSTOREINMEM pThis = (PRTCRSTOREINMEM)pvProvider; + return VERR_NOT_FOUND; +} + + +/** @interface_method_impl{RTCRSTOREPROVIDER,pfnCertFindAll} */ +static DECLCALLBACK(int) rtCrStoreInMem_CertFindAll(void *pvProvider, PRTCRSTORECERTSEARCH pSearch) +{ + pSearch->auOpaque[0] = ~(uintptr_t)pvProvider; + pSearch->auOpaque[1] = 0; + pSearch->auOpaque[2] = ~(uintptr_t)0; /* For the front-end API. */ + pSearch->auOpaque[3] = ~(uintptr_t)0; /* For the front-end API. */ + return VINF_SUCCESS; +} + + +/** @interface_method_impl{RTCRSTOREPROVIDER,pfnCertSearchNext} */ +static DECLCALLBACK(PCRTCRCERTCTX) rtCrStoreInMem_CertSearchNext(void *pvProvider, PRTCRSTORECERTSEARCH pSearch) +{ + PRTCRSTOREINMEM pThis = (PRTCRSTOREINMEM)pvProvider; + if (pSearch->auOpaque[0] == ~(uintptr_t)pvProvider) + { + uintptr_t i = pSearch->auOpaque[1]; + if (i < pThis->cCerts) + { + pSearch->auOpaque[1] = i + 1; + PRTCRCERTCTXINT pCertCtx = &pThis->papCerts[i]->Core; + ASMAtomicIncU32(&pCertCtx->cRefs); + return &pCertCtx->Public; + } + + /* Do we have a parent store to search? */ + if (pThis->hParentStore == NIL_RTCRSTORE) + return NULL; /* no */ + if ( !pThis->pParentProvider->pfnCertFindAll + || !pThis->pParentProvider->pfnCertSearchNext) + return NULL; + + RTCRSTORECERTSEARCH const SavedSearch = *pSearch; + int rc = pThis->pParentProvider->pfnCertFindAll(pThis->pvParentProvider, pSearch); + AssertRCReturnStmt(rc, *pSearch = SavedSearch, NULL); + + /* Restore the store.cpp specifics: */ + AssertCompile(RT_ELEMENTS(SavedSearch.auOpaque) == 4); + pSearch->auOpaque[2] = SavedSearch.auOpaque[2]; + pSearch->auOpaque[3] = SavedSearch.auOpaque[3]; + } + + AssertReturn(pThis->pParentProvider, NULL); + AssertReturn(pThis->pParentProvider->pfnCertSearchNext, NULL); + return pThis->pParentProvider->pfnCertSearchNext(pThis->pvParentProvider, pSearch); +} + + +/** @interface_method_impl{RTCRSTOREPROVIDER,pfnCertSearchDestroy} */ +static DECLCALLBACK(void) rtCrStoreInMem_CertSearchDestroy(void *pvProvider, PRTCRSTORECERTSEARCH pSearch) +{ + PRTCRSTOREINMEM pThis = (PRTCRSTOREINMEM)pvProvider; + if (pSearch->auOpaque[0] == ~(uintptr_t)pvProvider) + { + pSearch->auOpaque[0] = 0; + pSearch->auOpaque[1] = 0; + pSearch->auOpaque[2] = 0; + pSearch->auOpaque[3] = 0; + } + else + { + AssertReturnVoid(pThis->pParentProvider); + AssertReturnVoid(pThis->pParentProvider->pfnCertSearchDestroy); + pThis->pParentProvider->pfnCertSearchDestroy(pThis->pvParentProvider, pSearch); + } +} + + +/** @interface_method_impl{RTCRSTOREPROVIDER,pfnCertSearchDestroy} */ +static DECLCALLBACK(int) rtCrStoreInMem_CertAddEncoded(void *pvProvider, uint32_t fFlags, + uint8_t const *pbEncoded, uint32_t cbEncoded, PRTERRINFO pErrInfo) +{ + PRTCRSTOREINMEM pThis = (PRTCRSTOREINMEM)pvProvider; + int rc; + + AssertMsgReturn( (fFlags & RTCRCERTCTX_F_ENC_MASK) == RTCRCERTCTX_F_ENC_X509_DER + || (fFlags & RTCRCERTCTX_F_ENC_MASK) == RTCRCERTCTX_F_ENC_TAF_DER + , ("Only X.509 and TAF DER are supported: %#x\n", fFlags), VERR_INVALID_FLAGS); + + /* + * Check for duplicates if specified. + */ + if (fFlags & RTCRCERTCTX_F_ADD_IF_NOT_FOUND) + { + uint32_t iCert = pThis->cCerts; + while (iCert-- > 0) + { + PRTCRSTOREINMEMCERT pCert = pThis->papCerts[iCert]; + if ( pCert->Core.Public.cbEncoded == cbEncoded + && pCert->Core.Public.fFlags == (fFlags & RTCRCERTCTX_F_ENC_MASK) + && memcmp(pCert->Core.Public.pabEncoded, pbEncoded, cbEncoded) == 0) + return VWRN_ALREADY_EXISTS; + } + } + + /* + * Add it. + */ + if (pThis->cCerts + 1 <= pThis->cCertsAlloc) + { /* likely */ } + else + { + rc = rtCrStoreInMemGrow(pThis, pThis->cCerts + 1); + if (RT_FAILURE(rc)) + return rc; + } + + rc = rtCrStoreInMemCreateCertEntry(pThis, fFlags & RTCRCERTCTX_F_ENC_MASK, pbEncoded, cbEncoded, + pErrInfo, &pThis->papCerts[pThis->cCerts]); + if (RT_SUCCESS(rc)) + { + pThis->cCerts++; + return VINF_SUCCESS; + } + return rc; +} + + +/** + * In-memory store provider. + */ +static RTCRSTOREPROVIDER const g_rtCrStoreInMemProvider = +{ + "in-memory", + rtCrStoreInMem_DestroyStore, + rtCrStoreInMem_CertCtxQueryPrivateKey, + rtCrStoreInMem_CertFindAll, + rtCrStoreInMem_CertSearchNext, + rtCrStoreInMem_CertSearchDestroy, + rtCrStoreInMem_CertAddEncoded, + NULL, + 42 +}; + + +/** + * Common worker for RTCrStoreCreateInMem and future constructors... + * + * @returns IPRT status code. + * @param ppStore Where to return the store instance. + * @param hParentStore Optional parent store. Consums reference on + * success. + */ +static int rtCrStoreInMemCreateInternal(PRTCRSTOREINMEM *ppStore, RTCRSTORE hParentStore) +{ + PRTCRSTOREINMEM pStore = (PRTCRSTOREINMEM)RTMemAlloc(sizeof(*pStore)); + if (pStore) + { + pStore->cCerts = 0; + pStore->cCertsAlloc = 0; + pStore->papCerts = NULL; + pStore->hParentStore = hParentStore; + pStore->pParentProvider = NULL; + pStore->pvParentProvider = NULL; + *ppStore = pStore; + if (hParentStore == NIL_RTCRSTORE) + return VINF_SUCCESS; + if (~(uintptr_t)hParentStore != ~(uintptr_t)pStore) + { + pStore->pParentProvider = rtCrStoreGetProvider(hParentStore, &pStore->pvParentProvider); + if (pStore->pParentProvider) + return VINF_SUCCESS; + AssertFailed(); + } + RTMemFree(pStore); + } + *ppStore = NULL; /* shut up gcc-maybe-pita warning. */ + return VERR_NO_MEMORY; +} + + +RTDECL(int) RTCrStoreCreateInMemEx(PRTCRSTORE phStore, uint32_t cSizeHint, RTCRSTORE hParentStore) +{ + if (hParentStore != NIL_RTCRSTORE) + { + uint32_t cRefs = RTCrStoreRetain(hParentStore); + AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE); + } + + PRTCRSTOREINMEM pStore; + int rc = rtCrStoreInMemCreateInternal(&pStore, hParentStore); + if (RT_SUCCESS(rc)) + { + if (cSizeHint) + rc = rtCrStoreInMemGrow(pStore, RT_MIN(cSizeHint, 512)); + if (RT_SUCCESS(rc)) + { + rc = rtCrStoreCreate(&g_rtCrStoreInMemProvider, pStore, phStore); + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + } + RTMemFree(pStore); + } + + RTCrStoreRelease(hParentStore); + return rc; +} +RT_EXPORT_SYMBOL(RTCrStoreCreateInMemEx); + + +RTDECL(int) RTCrStoreCreateInMem(PRTCRSTORE phStore, uint32_t cSizeHint) +{ + return RTCrStoreCreateInMemEx(phStore, cSizeHint, NIL_RTCRSTORE); +} +RT_EXPORT_SYMBOL(RTCrStoreCreateInMem); + diff --git a/src/VBox/Runtime/common/crypto/store-internal.h b/src/VBox/Runtime/common/crypto/store-internal.h new file mode 100644 index 00000000..f9ae6724 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/store-internal.h @@ -0,0 +1,177 @@ +/* $Id: store-internal.h $ */ +/** @file + * IPRT - Cryptographic Store, Internal Header. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef IPRT_INCLUDED_SRC_common_crypto_store_internal_h +#define IPRT_INCLUDED_SRC_common_crypto_store_internal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + + +/** + * Internal certificate context. + * + * In addition to the externally visible structure (RTCRCERTCTX) this has the + * reference counter and store reference. (This structure may again be part of + * a larger structure internal to the store, depending on the source store.) + */ +typedef struct RTCRCERTCTXINT +{ + /** Magic number (RTCRCERTCTXINT_MAGIC). */ + uint32_t u32Magic; + /** Reference counter. */ + uint32_t volatile cRefs; + /** + * Destructor that gets called with cRefs reaches zero. + * @param pCertCtx The internal certificate context. + */ + DECLCALLBACKMEMBER(void, pfnDtor,(struct RTCRCERTCTXINT *pCertCtx)); + /** The public store context. */ + RTCRCERTCTX Public; +} RTCRCERTCTXINT; +/** Pointer to an internal certificate context. */ +typedef RTCRCERTCTXINT *PRTCRCERTCTXINT; + +/** Magic value for RTCRCERTCTXINT::u32Magic (Alan Mathison Turing). */ +#define RTCRCERTCTXINT_MAGIC UINT32_C(0x19120623) +/** Dead magic value for RTCRCERTCTXINT::u32Magic. */ +#define RTCRCERTCTXINT_MAGIC_DEAD UINT32_C(0x19540607) + + +/** + * IPRT Cryptographic Store Provider. + * + * @remarks This is a very incomplete sketch. + */ +typedef struct RTCRSTOREPROVIDER +{ + /** The provider name. */ + const char *pszName; + + /** + * Called to destroy an open store. + * + * @param pvProvider The provider specific data. + */ + DECLCALLBACKMEMBER(void, pfnDestroyStore,(void *pvProvider)); + + /** + * Queries the private key. + * + * @returns IPRT status code. + * @retval VERR_NOT_FOUND if not private key. + * @retval VERR_ACCESS_DENIED if the private key isn't allowed to leave the + * store. One would then have to use the pfnCertCtxSign method. + * + * @param pvProvider The provider specific data. + * @param pCertCtx The internal certificate context. + * @param pbKey Where to return the key bytes. + * @param cbKey The size of the buffer @a pbKey points to. + * @param pcbKeyRet Where to return the size of the returned key. + */ + DECLCALLBACKMEMBER(int, pfnCertCtxQueryPrivateKey,(void *pvProvider, PRTCRCERTCTXINT pCertCtx, + uint8_t *pbKey, size_t cbKey, size_t *pcbKeyRet)); + + /** + * Open an enumeration of all certificates. + * + * @returns IPRT status code + * @param pvProvider The provider specific data. + * @param pSearch Pointer to opaque search state structure. The + * provider should initalize this on success. + */ + DECLCALLBACKMEMBER(int, pfnCertFindAll,(void *pvProvider, PRTCRSTORECERTSEARCH pSearch)); + + /** + * Get the next certificate. + * + * @returns Reference to the next certificate context (must be released by + * caller). NULL if no more certificates in the search result. + * @param pvProvider The provider specific data. + * @param pSearch Pointer to opaque search state structure. + */ + DECLCALLBACKMEMBER(PCRTCRCERTCTX, pfnCertSearchNext,(void *pvProvider, PRTCRSTORECERTSEARCH pSearch)); + + /** + * Closes a certficate search state. + * + * @param pvProvider The provider specific data. + * @param pSearch Pointer to opaque search state structure to destroy. + */ + DECLCALLBACKMEMBER(void, pfnCertSearchDestroy,(void *pvProvider, PRTCRSTORECERTSEARCH pSearch)); + + /** + * Adds a certificate to the store. + * + * @returns IPRT status code. + * @retval VWRN_ALREADY_EXISTS if the certificate is already present and + * RTCRCERTCTX_F_ADD_IF_NOT_FOUND was specified. + * @param pvProvider The provider specific data. + * @param fFlags RTCRCERTCTX_F_XXX. + * @param pbEncoded The encoded certificate bytes. + * @param cbEncoded The size of the encoded certificate. + * @param pErrInfo Where to store extended error info. Optional. + */ + DECLCALLBACKMEMBER(int, pfnCertAddEncoded,(void *pvProvider, uint32_t fFlags, uint8_t const *pbEncoded, uint32_t cbEncoded, + PRTERRINFO pErrInfo)); + + + /* Optional: */ + + /** + * Find all certficates matching a given issuer and serial number. + * + * (Usually only one result.) + * + * @returns IPRT status code + * @param pvProvider The provider specific data. + * @param phSearch Pointer to a provider specific search handle. + */ + DECLCALLBACKMEMBER(int, pfnCertFindByIssuerAndSerialNo,(void *pvProvider, PCRTCRX509NAME pIssuer, PCRTASN1INTEGER pSerialNo, + PRTCRSTORECERTSEARCH phSearch)); + /** Non-zero end marker. */ + uintptr_t uEndMarker; +} RTCRSTOREPROVIDER; + +/** Pointer to a store provider call table. */ +typedef RTCRSTOREPROVIDER const *PCRTCRSTOREPROVIDER; + + +DECLHIDDEN(int) rtCrStoreCreate(PCRTCRSTOREPROVIDER pProvider, void *pvProvider, PRTCRSTORE phStore); +DECLHIDDEN(PCRTCRSTOREPROVIDER) rtCrStoreGetProvider(RTCRSTORE hStore, void **ppvProvider); + +#endif /* !IPRT_INCLUDED_SRC_common_crypto_store_internal_h */ + diff --git a/src/VBox/Runtime/common/crypto/store.cpp b/src/VBox/Runtime/common/crypto/store.cpp new file mode 100644 index 00000000..0d9323da --- /dev/null +++ b/src/VBox/Runtime/common/crypto/store.cpp @@ -0,0 +1,571 @@ +/* $Id: store.cpp $ */ +/** @file + * IPRT - Cryptographic (Certificate) Store. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/store.h> + +#include <iprt/asm.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/string.h> + +#include <iprt/crypto/pkcs7.h> +#include <iprt/crypto/x509.h> + +#ifdef IPRT_WITH_OPENSSL +# include "internal/openssl-pre.h" +# include <openssl/x509.h> +# include "internal/openssl-post.h" +#endif + +#include "store-internal.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Internal representation of a (certificate,++) store. + */ +typedef struct RTCRSTOREINT +{ + /** Magic number (RTCRSTOREINT_MAGIC). */ + uint32_t u32Magic; + /** Reference counter. */ + uint32_t volatile cRefs; + /** Pointer to the store provider. */ + PCRTCRSTOREPROVIDER pProvider; + /** Provider specific data. */ + void *pvProvider; +} RTCRSTOREINT; +/** Pointer to the internal representation of a store. */ +typedef RTCRSTOREINT *PRTCRSTOREINT; + +/** Magic value for RTCRSTOREINT::u32Magic (Alfred Dillwyn "Dilly" Knox). */ +#define RTCRSTOREINT_MAGIC UINT32_C(0x18840723) +/** Dead magic value for RTCRSTOREINT::u32Magic. */ +#define RTCRSTOREINT_MAGIC_DEAD UINT32_C(0x19430227) + + + +/** + * Internal method a store provider uses to create a store handle. + * + * @returns IPRT status code + * @param pProvider Pointer to the store provider callback table. + * @param pvProvider Pointer to the provider specific instance data. + * @param phStore Where to return the store handle. + */ +DECLHIDDEN(int) rtCrStoreCreate(PCRTCRSTOREPROVIDER pProvider, void *pvProvider, PRTCRSTORE phStore) +{ + PRTCRSTOREINT pThis = (PRTCRSTOREINT)RTMemAlloc(sizeof(*pThis)); + if (pThis) + { + pThis->pvProvider = pvProvider; + pThis->pProvider = pProvider; + pThis->cRefs = 1; + pThis->u32Magic = RTCRSTOREINT_MAGIC; + *phStore = pThis; + return VINF_SUCCESS; + } + return VERR_NO_MEMORY; +} + + +/** + * For the parent forwarding of the in-memory store. + */ +DECLHIDDEN(PCRTCRSTOREPROVIDER) rtCrStoreGetProvider(RTCRSTORE hStore, void **ppvProvider) +{ + PRTCRSTOREINT pThis = (PRTCRSTOREINT)hStore; + AssertPtrReturn(pThis, NULL); + AssertReturn(pThis->u32Magic == RTCRSTOREINT_MAGIC, NULL); + *ppvProvider = pThis->pvProvider; + return pThis->pProvider; +} + + +RTDECL(uint32_t) RTCrStoreRetain(RTCRSTORE hStore) +{ + PRTCRSTOREINT pThis = (PRTCRSTOREINT)hStore; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTCRSTOREINT_MAGIC, UINT32_MAX); + + uint32_t cRet = ASMAtomicIncU32(&pThis->cRefs); + Assert(cRet < 8192); + return cRet; +} + + +RTDECL(uint32_t) RTCrStoreRelease(RTCRSTORE hStore) +{ + if (hStore == NIL_RTCRSTORE) + return 0; + + PRTCRSTOREINT pThis = (PRTCRSTOREINT)hStore; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTCRSTOREINT_MAGIC, UINT32_MAX); + + uint32_t cStore = ASMAtomicDecU32(&pThis->cRefs); + if (!cStore) + { + ASMAtomicWriteU32(&pThis->u32Magic, RTCRSTOREINT_MAGIC_DEAD); + pThis->pProvider->pfnDestroyStore(pThis->pvProvider); + RTMemFree(pThis); + } + return cStore; +} + + +RTDECL(PCRTCRCERTCTX) RTCrStoreCertByIssuerAndSerialNo(RTCRSTORE hStore, PCRTCRX509NAME pIssuer, PCRTASN1INTEGER pSerialNo) +{ + PRTCRSTOREINT pThis = (PRTCRSTOREINT)hStore; + AssertPtrReturn(pThis, NULL); + AssertReturn(pThis->u32Magic == RTCRSTOREINT_MAGIC, NULL); + AssertPtrReturn(pIssuer, NULL); + + int rc; + RTCRSTORECERTSEARCH Search; + if (pThis->pProvider->pfnCertFindByIssuerAndSerialNo) + rc = pThis->pProvider->pfnCertFindByIssuerAndSerialNo(pThis->pvProvider, pIssuer, pSerialNo, &Search); + else + rc = pThis->pProvider->pfnCertFindAll(pThis->pvProvider, &Search); + + PCRTCRCERTCTX pCertCtx = NULL; + if (RT_SUCCESS(rc)) + { + for (;;) + { + pCertCtx = pThis->pProvider->pfnCertSearchNext(pThis->pvProvider, &Search); + if (!pCertCtx) + break; + + if ( pCertCtx->pCert + && RTCrX509Certificate_MatchIssuerAndSerialNumber(pCertCtx->pCert, pIssuer, pSerialNo)) + break; + RTCrCertCtxRelease(pCertCtx); + } + + pThis->pProvider->pfnCertSearchDestroy(pThis->pvProvider, &Search); + } + else + AssertMsg(rc == VERR_NOT_FOUND, ("%Rrc\n", rc)); + return pCertCtx; +} + + +RTDECL(int) RTCrStoreCertAddEncoded(RTCRSTORE hStore, uint32_t fFlags, void const *pvSrc, size_t cbSrc, PRTERRINFO pErrInfo) +{ + PRTCRSTOREINT pThis = (PRTCRSTOREINT)hStore; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRSTOREINT_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pvSrc, VERR_INVALID_POINTER); + AssertReturn(cbSrc > 16 && cbSrc < _1M, VERR_OUT_OF_RANGE); + AssertReturn(!(fFlags & ~(RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ENC_MASK)), VERR_INVALID_FLAGS); + AssertMsgReturn( (fFlags & RTCRCERTCTX_F_ENC_MASK) == RTCRCERTCTX_F_ENC_X509_DER + || (fFlags & RTCRCERTCTX_F_ENC_MASK) == RTCRCERTCTX_F_ENC_TAF_DER + , ("Only X.509 and TAF DER supported: %#x\n", fFlags), VERR_INVALID_FLAGS); + + int rc; + if (pThis->pProvider->pfnCertAddEncoded) + rc = pThis->pProvider->pfnCertAddEncoded(pThis->pvProvider, fFlags, (uint8_t const *)pvSrc, (uint32_t)cbSrc, pErrInfo); + else + rc = VERR_WRITE_PROTECT; + + return rc; +} + + +RTDECL(int) RTCrStoreCertAddX509(RTCRSTORE hStore, uint32_t fFlags, PRTCRX509CERTIFICATE pCertificate, PRTERRINFO pErrInfo) +{ + /* + * Validate. + */ + PRTCRSTOREINT pThis = (PRTCRSTOREINT)hStore; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRSTOREINT_MAGIC, VERR_INVALID_HANDLE); + + AssertPtrReturn(pCertificate, VERR_INVALID_POINTER); + AssertReturn(RTCrX509Certificate_IsPresent(pCertificate), VERR_INVALID_PARAMETER); + int rc = RTCrX509Certificate_CheckSanity(pCertificate, 0, pErrInfo, "Cert"); + AssertRCReturn(rc, rc); + + AssertReturn(!(fFlags & ~(RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ENC_MASK)), VERR_INVALID_FLAGS); + AssertCompile(RTCRCERTCTX_F_ENC_X509_DER == 0); + AssertMsgReturn((fFlags & RTCRCERTCTX_F_ENC_MASK) == RTCRCERTCTX_F_ENC_X509_DER, + ("Invalid encoding: %#x\n", fFlags), VERR_INVALID_FLAGS); + + /* + * Encode and add it using pfnCertAddEncoded. + */ + if (pThis->pProvider->pfnCertAddEncoded) + { + PRTASN1CORE pCore = RTCrX509Certificate_GetAsn1Core(pCertificate); + uint32_t cbEncoded = 0; + rc = RTAsn1EncodePrepare(pCore, RTASN1ENCODE_F_DER, &cbEncoded, pErrInfo); + if (RT_SUCCESS(rc)) + { + uint8_t * const pbEncoded = (uint8_t *)RTMemTmpAllocZ(cbEncoded); + if (pbEncoded) + { + rc = RTAsn1EncodeToBuffer(pCore, RTASN1ENCODE_F_DER, pbEncoded, cbEncoded, pErrInfo); + if (RT_SUCCESS(rc)) + rc = pThis->pProvider->pfnCertAddEncoded(pThis->pvProvider, fFlags, pbEncoded, cbEncoded, pErrInfo); + RTMemTmpFree(pbEncoded); + } + else + rc = VERR_NO_TMP_MEMORY; + } + } + else + rc = VERR_WRITE_PROTECT; + + return rc; +} + + +RTDECL(int) RTCrStoreCertAddPkcs7(RTCRSTORE hStore, uint32_t fFlags, PRTCRPKCS7CERT pCertificate, PRTERRINFO pErrInfo) +{ + AssertPtrReturn(pCertificate, VERR_INVALID_POINTER); + AssertReturn(RTCrPkcs7Cert_IsPresent(pCertificate), VERR_INVALID_PARAMETER); + switch (pCertificate->enmChoice) + { + case RTCRPKCS7CERTCHOICE_X509: + return RTCrStoreCertAddX509(hStore, fFlags, pCertificate->u.pX509Cert, pErrInfo); + + case RTCRPKCS7CERTCHOICE_EXTENDED_PKCS6: + return RTErrInfoSetF(pErrInfo, VERR_NOT_IMPLEMENTED, "RTCrStoreCertAddPkcs7 does not implement EXTENDED_PKCS6"); + case RTCRPKCS7CERTCHOICE_AC_V1: + return RTErrInfoSetF(pErrInfo, VERR_NOT_IMPLEMENTED, "RTCrStoreCertAddPkcs7 does not implement AC_V1"); + case RTCRPKCS7CERTCHOICE_AC_V2: + return RTErrInfoSetF(pErrInfo, VERR_NOT_IMPLEMENTED, "RTCrStoreCertAddPkcs7 does not implement AC_V2"); + case RTCRPKCS7CERTCHOICE_OTHER: + return RTErrInfoSetF(pErrInfo, VERR_NOT_IMPLEMENTED, "RTCrStoreCertAddPkcs7 does not implement OTHER"); + case RTCRPKCS7CERTCHOICE_END: + case RTCRPKCS7CERTCHOICE_INVALID: + case RTCRPKCS7CERTCHOICE_32BIT_HACK: + break; + /* no default */ + } + return RTErrInfoSetF(pErrInfo, VERR_INVALID_PARAMETER, "Invalid RTCRPKCS7CERT enmChoice value: %d", pCertificate->enmChoice); +} + + +/* + * Searching. + * Searching. + * Searching. + */ + +RTDECL(int) RTCrStoreCertFindAll(RTCRSTORE hStore, PRTCRSTORECERTSEARCH pSearch) +{ + PRTCRSTOREINT pThis = (PRTCRSTOREINT)hStore; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRSTOREINT_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pSearch, VERR_INVALID_POINTER); + + return pThis->pProvider->pfnCertFindAll(pThis->pvProvider, pSearch); +} + + +/** Indicator for RTCrStoreCertFindBySubjectOrAltSubjectByRfc5280 searches + * implemented by this front-end code. */ +#define RTCRSTORECERTSEARCH_BY_SUBECT_OR_ALT_SUBJECT_BY_RFC5280 UINT32_C(0x5be9145d) + +RTDECL(int) RTCrStoreCertFindBySubjectOrAltSubjectByRfc5280(RTCRSTORE hStore, PCRTCRX509NAME pSubject, + PRTCRSTORECERTSEARCH pSearch) +{ + PRTCRSTOREINT pThis = (PRTCRSTOREINT)hStore; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRSTOREINT_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pSearch, VERR_INVALID_POINTER); + + int rc = pThis->pProvider->pfnCertFindAll(pThis->pvProvider, pSearch); + if (RT_SUCCESS(rc)) + { + pSearch->auOpaque[2] = RTCRSTORECERTSEARCH_BY_SUBECT_OR_ALT_SUBJECT_BY_RFC5280; + pSearch->auOpaque[3] = (uintptr_t)pSubject; + } + return rc; +} + + +RTDECL(PCRTCRCERTCTX) RTCrStoreCertSearchNext(RTCRSTORE hStore, PRTCRSTORECERTSEARCH pSearch) +{ + PRTCRSTOREINT pThis = (PRTCRSTOREINT)hStore; + AssertPtrReturn(pThis, NULL); + AssertReturn(pThis->u32Magic == RTCRSTOREINT_MAGIC, NULL); + AssertPtrReturn(pSearch, NULL); + + PCRTCRCERTCTX pRet; + switch (pSearch->auOpaque[2]) + { + default: + pRet = pThis->pProvider->pfnCertSearchNext(pThis->pvProvider, pSearch); + break; + + case RTCRSTORECERTSEARCH_BY_SUBECT_OR_ALT_SUBJECT_BY_RFC5280: + { + PCRTCRX509NAME pSubject = (PCRTCRX509NAME)pSearch->auOpaque[3]; + AssertPtrReturn(pSubject, NULL); + + for (;;) + { + pRet = pThis->pProvider->pfnCertSearchNext(pThis->pvProvider, pSearch); + if (!pRet) + break; + if (pRet->pCert) + { + if (RTCrX509Certificate_MatchSubjectOrAltSubjectByRfc5280(pRet->pCert, pSubject)) + break; + } + else if (pRet->pTaInfo) + { + if ( RTCrTafCertPathControls_IsPresent(&pRet->pTaInfo->CertPath) + && RTCrX509Name_MatchByRfc5280(&pRet->pTaInfo->CertPath.TaName, pSubject)) + break; + } + RTCrCertCtxRelease(pRet); + } + break; + } + } + return pRet; +} + + +RTDECL(int) RTCrStoreCertSearchDestroy(RTCRSTORE hStore, PRTCRSTORECERTSEARCH pSearch) +{ + PRTCRSTOREINT pThis = (PRTCRSTOREINT)hStore; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRSTOREINT_MAGIC, VERR_INVALID_HANDLE); + if (pSearch) + { + AssertPtrReturn(pSearch, VERR_INVALID_POINTER); + pThis->pProvider->pfnCertSearchDestroy(pThis->pvProvider, pSearch); + } + return VINF_SUCCESS; +} + + + +RTDECL(uint32_t) RTCrStoreCertCount(RTCRSTORE hStore) +{ + PRTCRSTOREINT pThis = (PRTCRSTOREINT)hStore; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTCRSTOREINT_MAGIC, UINT32_MAX); + + RTCRSTORECERTSEARCH Search; + int rc = pThis->pProvider->pfnCertFindAll(pThis->pvProvider, &Search); + AssertRCReturn(rc, UINT32_MAX); + + + uint32_t cCerts = 0; + PCRTCRCERTCTX pCur; + while ((pCur = pThis->pProvider->pfnCertSearchNext(pThis->pvProvider, &Search)) != NULL) + { + RTCrCertCtxRelease(pCur); + cCerts++; + } + + return cCerts; +} + + +#ifdef IPRT_WITH_OPENSSL +/* + * OpenSSL helper. + * OpenSSL helper. + * OpenSSL helper. + */ + +RTDECL(int) RTCrStoreConvertToOpenSslCertStore(RTCRSTORE hStore, uint32_t fFlags, void **ppvOpenSslStore, PRTERRINFO pErrInfo) +{ + RT_NOREF(pErrInfo); + PRTCRSTOREINT pThis = (PRTCRSTOREINT)hStore; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRSTOREINT_MAGIC, VERR_INVALID_HANDLE); + RT_NOREF_PV(fFlags); + + /* + * Use the pfnCertFindAll method to add all certificates to the store we're returning. + */ + int rc; + X509_STORE *pOsslStore = X509_STORE_new(); + if (pOsslStore) + { + RTCRSTORECERTSEARCH Search; + rc = pThis->pProvider->pfnCertFindAll(pThis->pvProvider, &Search); + if (RT_SUCCESS(rc)) + { + do + { + PCRTCRCERTCTX pCertCtx = pThis->pProvider->pfnCertSearchNext(pThis->pvProvider, &Search); + if (!pCertCtx) + break; + + if ( (pCertCtx->fFlags & RTCRCERTCTX_F_ENC_MASK) == RTCRCERTCTX_F_ENC_X509_DER + && pCertCtx->cbEncoded > 0) + { + X509 *pOsslCert = NULL; + const unsigned char *pabEncoded = (const unsigned char *)pCertCtx->pabEncoded; + if (d2i_X509(&pOsslCert, &pabEncoded, pCertCtx->cbEncoded) == pOsslCert && pOsslCert != NULL) + { + if (!X509_STORE_add_cert(pOsslStore, pOsslCert)) + rc = VERR_NO_MEMORY; + X509_free(pOsslCert); + } + } + + RTCrCertCtxRelease(pCertCtx); + } while (RT_SUCCESS(rc)); + + pThis->pProvider->pfnCertSearchDestroy(pThis->pvProvider, &Search); + if (RT_SUCCESS(rc)) + { + *ppvOpenSslStore = pOsslStore; + return VINF_SUCCESS; + } + } + X509_STORE_free(pOsslStore); + } + else + rc = VERR_NO_MEMORY; + return rc; +} + + +RTDECL(int) RTCrStoreConvertToOpenSslCertStack(RTCRSTORE hStore, uint32_t fFlags, void **ppvOpenSslStack, PRTERRINFO pErrInfo) +{ + RT_NOREF(pErrInfo); + PRTCRSTOREINT pThis = (PRTCRSTOREINT)hStore; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRSTOREINT_MAGIC, VERR_INVALID_HANDLE); + RT_NOREF_PV(fFlags); + + /* + * Use the pfnCertFindAll method to add all certificates to the store we're returning. + */ + int rc; + STACK_OF(X509) *pOsslStack = sk_X509_new_null(); + if (pOsslStack) + { + RTCRSTORECERTSEARCH Search; + rc = pThis->pProvider->pfnCertFindAll(pThis->pvProvider, &Search); + if (RT_SUCCESS(rc)) + { + do + { + PCRTCRCERTCTX pCertCtx = pThis->pProvider->pfnCertSearchNext(pThis->pvProvider, &Search); + if (!pCertCtx) + break; + + if ( (pCertCtx->fFlags & RTCRCERTCTX_F_ENC_MASK) == RTCRCERTCTX_F_ENC_X509_DER + && pCertCtx->cbEncoded > 0) + { + X509 *pOsslCert = NULL; + const unsigned char *pabEncoded = (const unsigned char *)pCertCtx->pabEncoded; + if (d2i_X509(&pOsslCert, &pabEncoded, pCertCtx->cbEncoded) == pOsslCert && pOsslCert != NULL) + { + if (!sk_X509_push(pOsslStack, pOsslCert)) + { + rc = VERR_NO_MEMORY; + X509_free(pOsslCert); + } + } + } + + RTCrCertCtxRelease(pCertCtx); + } while (RT_SUCCESS(rc)); + + pThis->pProvider->pfnCertSearchDestroy(pThis->pvProvider, &Search); + if (RT_SUCCESS(rc)) + { + *ppvOpenSslStack = pOsslStack; + return VINF_SUCCESS; + } + } +#include "internal/openssl-pre.h" /* Need to disable C5039 warning here. */ + sk_X509_pop_free(pOsslStack, X509_free); +#include "internal/openssl-post.h" + } + else + rc = VERR_NO_MEMORY; + return rc; +} + +#endif /* IPRT_WITH_OPENSSL */ + + +/* + * Certificate context. + * Certificate context. + * Certificate context. + */ + + +RTDECL(uint32_t) RTCrCertCtxRetain(PCRTCRCERTCTX pCertCtx) +{ + AssertPtrReturn(pCertCtx, UINT32_MAX); + PRTCRCERTCTXINT pThis = RT_FROM_MEMBER(pCertCtx, RTCRCERTCTXINT, Public); + AssertReturn(pThis->u32Magic == RTCRCERTCTXINT_MAGIC, UINT32_MAX); + uint32_t cRet = ASMAtomicIncU32(&pThis->cRefs); + Assert(cRet < 64); + return cRet; +} + + +RTDECL(uint32_t) RTCrCertCtxRelease(PCRTCRCERTCTX pCertCtx) +{ + if (!pCertCtx) + return 0; + + AssertPtrReturn(pCertCtx, UINT32_MAX); + PRTCRCERTCTXINT pThis = RT_FROM_MEMBER(pCertCtx, RTCRCERTCTXINT, Public); + AssertReturn(pThis->u32Magic == RTCRCERTCTXINT_MAGIC, UINT32_MAX); + uint32_t cRet = ASMAtomicDecU32(&pThis->cRefs); + if (!cRet) + { + ASMAtomicWriteU32(&pThis->u32Magic, RTCRCERTCTXINT_MAGIC_DEAD); + pThis->pfnDtor(pThis); + } + return cRet; +} + diff --git a/src/VBox/Runtime/common/crypto/taf-asn1-decoder.cpp b/src/VBox/Runtime/common/crypto/taf-asn1-decoder.cpp new file mode 100644 index 00000000..586aab90 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/taf-asn1-decoder.cpp @@ -0,0 +1,54 @@ +/* $Id: taf-asn1-decoder.cpp $ */ +/** @file + * IPRT - Crypto - Trust Anchor Format (RFC-5914), Decoder for ASN.1. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/taf.h> + +#include <iprt/errcore.h> +#include <iprt/mem.h> +#include <iprt/string.h> + +#include "taf-internal.h" + +/* + * Generate the code. + */ +#include <iprt/asn1-generator-asn1-decoder.h> + diff --git a/src/VBox/Runtime/common/crypto/taf-core.cpp b/src/VBox/Runtime/common/crypto/taf-core.cpp new file mode 100644 index 00000000..c66233e0 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/taf-core.cpp @@ -0,0 +1,51 @@ +/* $Id: taf-core.cpp $ */ +/** @file + * IPRT - Crypto - Trust Anchor Format (RFC-5914), Core API. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/taf.h> + +#include "taf-internal.h" + + +/* + * Generate the code. + */ +#include <iprt/asn1-generator-core.h> + diff --git a/src/VBox/Runtime/common/crypto/taf-init.cpp b/src/VBox/Runtime/common/crypto/taf-init.cpp new file mode 100644 index 00000000..dc75ae11 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/taf-init.cpp @@ -0,0 +1,54 @@ +/* $Id: taf-init.cpp $ */ +/** @file + * IPRT - Crypto - Trust Anchor Format (RFC-5914), Initialization API. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/taf.h> + +#include <iprt/errcore.h> +#include <iprt/string.h> +#include <iprt/crypto/x509.h> + +#include "taf-internal.h" + +/* + * Generate the code. + */ +#include <iprt/asn1-generator-init.h> + diff --git a/src/VBox/Runtime/common/crypto/taf-internal.h b/src/VBox/Runtime/common/crypto/taf-internal.h new file mode 100644 index 00000000..1fe132e5 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/taf-internal.h @@ -0,0 +1,47 @@ +/* $Id: taf-internal.h $ */ +/** @file + * IPRT - Crypto - TAF, Internal Header. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef IPRT_INCLUDED_SRC_common_crypto_taf_internal_h +#define IPRT_INCLUDED_SRC_common_crypto_taf_internal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#define RTASN1TMPL_TEMPLATE_FILE "../common/crypto/taf-template.h" +#include <iprt/asn1-generator-internal-header.h> + +#endif /* !IPRT_INCLUDED_SRC_common_crypto_taf_internal_h */ + diff --git a/src/VBox/Runtime/common/crypto/taf-sanity.cpp b/src/VBox/Runtime/common/crypto/taf-sanity.cpp new file mode 100644 index 00000000..ba8a65b4 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/taf-sanity.cpp @@ -0,0 +1,52 @@ +/* $Id: taf-sanity.cpp $ */ +/** @file + * IPRT - Crypto - Trust Anchor Format (RFC-5914), Sanity Checkers. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/taf.h> + +#include <iprt/errcore.h> + +#include "taf-internal.h" + +/* + * Generate the code. + */ +#include <iprt/asn1-generator-sanity.h> + diff --git a/src/VBox/Runtime/common/crypto/taf-template.h b/src/VBox/Runtime/common/crypto/taf-template.h new file mode 100644 index 00000000..d42e0d9b --- /dev/null +++ b/src/VBox/Runtime/common/crypto/taf-template.h @@ -0,0 +1,104 @@ +/* $Id: taf-template.h $ */ +/** @file + * IPRT - Crypto - Trust Anchor Format (RFC-5914), Code Generator Template. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#define RTASN1TMPL_DECL RTDECL + +/* + * CertPathControls (not sequence-/set-of). + */ +#define RTASN1TMPL_TYPE RTCRTAFCERTPATHCONTROLS +#define RTASN1TMPL_EXT_NAME RTCrTafCertPathControls +#define RTASN1TMPL_INT_NAME rtCrTafCertPathControls +RTASN1TMPL_BEGIN_SEQCORE(); +RTASN1TMPL_MEMBER( TaName, RTCRX509NAME, RTCrX509Name); +RTASN1TMPL_MEMBER_OPT_ITAG( Certificate, RTCRX509CERTIFICATE, RTCrX509Certificate, 0); +RTASN1TMPL_MEMBER_OPT_ITAG( PolicySet, RTCRX509CERTIFICATEPOLICIES, RTCrX509CertificatePolicies, 1); +RTASN1TMPL_MEMBER_OPT_ITAG_BITSTRING(PolicyFlags, 3 /* max bits */, 2); +RTASN1TMPL_MEMBER_OPT_ITAG( NameConstr, RTCRX509NAMECONSTRAINTS, RTCrX509NameConstraints, 3); +RTASN1TMPL_MEMBER_OPT_ITAG_EX( PathLenConstraint, RTASN1INTEGER, RTAsn1Integer, 4, RTASN1TMPL_ITAG_F_CP, RT_NOTHING); +RTASN1TMPL_END_SEQCORE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * TrustAnchorInfo. + */ +#define RTASN1TMPL_TYPE RTCRTAFTRUSTANCHORINFO +#define RTASN1TMPL_EXT_NAME RTCrTafTrustAnchorInfo +#define RTASN1TMPL_INT_NAME rtCrTafTrustAnchorInfo +RTASN1TMPL_BEGIN_SEQCORE(); +RTASN1TMPL_MEMBER_DEF_ITAG_UP( Version, RTASN1INTEGER, RTAsn1Integer, ASN1_TAG_INTEGER, RTCRTAFTRUSTANCHORINFO_V1); +RTASN1TMPL_MEMBER( PubKey, RTCRX509SUBJECTPUBLICKEYINFO, RTCrX509SubjectPublicKeyInfo); +RTASN1TMPL_MEMBER( KeyIdentifier, RTASN1OCTETSTRING, RTAsn1OctetString); +RTASN1TMPL_MEMBER_OPT_UTF8_STRING_EX(TaTitle, RTASN1TMPL_MEMBER_CONSTR_MIN_MAX(TaTitleLangTag, RTASN1STRING, RTAsn1String, 1, 64, RT_NOTHING)); +RTASN1TMPL_MEMBER_OPT_ITAG_EX( CertPath, RTCRTAFCERTPATHCONTROLS, RTCrTafCertPathControls, ASN1_TAG_SEQUENCE, RTASN1TMPL_ITAG_F_UC, RT_NOTHING); +RTASN1TMPL_MEMBER_OPT_XTAG( T1, CtxTag1, Exts, RTCRX509EXTENSIONS, RTCrX509Extensions, 1); +RTASN1TMPL_MEMBER_OPT_UTF8_STRING_EX(TaTitleLangTag, RTASN1TMPL_MEMBER_CONSTR_MIN_MAX(TaTitleLangTag, RTASN1STRING, RTAsn1String, 2, 4, RT_NOTHING)); +RTASN1TMPL_END_SEQCORE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * TrustAnchorChoice. + */ +#define RTASN1TMPL_TYPE RTCRTAFTRUSTANCHORCHOICE +#define RTASN1TMPL_EXT_NAME RTCrTafTrustAnchorChoice +#define RTASN1TMPL_INT_NAME rtCrTafTrustAnchorChoice +RTASN1TMPL_BEGIN_PCHOICE(); +RTASN1TMPL_PCHOICE_ITAG(ASN1_TAG_SEQUENCE, RTCRTAFTRUSTANCHORCHOICEVAL_CERTIFICATE, u.pCertificate, Certificate, RTCRX509CERTIFICATE, RTCrX509Certificate); +RTASN1TMPL_PCHOICE_XTAG(1, RTCRTAFTRUSTANCHORCHOICEVAL_TBS_CERTIFICATE, u.pT1, CtxTag1, TbsCert, RTCRX509TBSCERTIFICATE, RTCrX509TbsCertificate); +RTASN1TMPL_PCHOICE_XTAG(2, RTCRTAFTRUSTANCHORCHOICEVAL_TRUST_ANCHOR_INFO, u.pT2, CtxTag2, TaInfo, RTCRTAFTRUSTANCHORINFO, RTCrTafTrustAnchorInfo); +RTASN1TMPL_END_PCHOICE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * TrustAnchorList + */ +#define RTASN1TMPL_TYPE RTCRTAFTRUSTANCHORLIST +#define RTASN1TMPL_EXT_NAME RTCrTafTrustAnchorList +#define RTASN1TMPL_INT_NAME rtCrTafTrustAnchorList +RTASN1TMPL_SEQ_OF(RTCRTAFTRUSTANCHORCHOICE, RTCrTafTrustAnchorChoice); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + diff --git a/src/VBox/Runtime/common/crypto/tsp-asn1-decoder.cpp b/src/VBox/Runtime/common/crypto/tsp-asn1-decoder.cpp new file mode 100644 index 00000000..6a4ba919 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/tsp-asn1-decoder.cpp @@ -0,0 +1,51 @@ +/* $Id: tsp-asn1-decoder.cpp $ */ +/** @file + * IPRT - Crypto - Time-Stamp Protocol (RFC-3161), Decoder for ASN.1. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/tsp.h> + +#include "tsp-internal.h" + + +/* + * Generate the code. + */ +#include <iprt/asn1-generator-asn1-decoder.h> + diff --git a/src/VBox/Runtime/common/crypto/tsp-core.cpp b/src/VBox/Runtime/common/crypto/tsp-core.cpp new file mode 100644 index 00000000..7e784943 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/tsp-core.cpp @@ -0,0 +1,51 @@ +/* $Id: tsp-core.cpp $ */ +/** @file + * IPRT - Crypto - Time-Stamp Protocol (RFC-3161), Core API. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/tsp.h> + +#include "tsp-internal.h" + + +/* + * Generate the code. + */ +#include <iprt/asn1-generator-core.h> + diff --git a/src/VBox/Runtime/common/crypto/tsp-init.cpp b/src/VBox/Runtime/common/crypto/tsp-init.cpp new file mode 100644 index 00000000..1a3ed635 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/tsp-init.cpp @@ -0,0 +1,51 @@ +/* $Id: tsp-init.cpp $ */ +/** @file + * IPRT - Crypto - Time-Stamp Protocol (RFC-3161), Initialization API. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/tsp.h> + +#include "tsp-internal.h" + + +/* + * Generate the code. + */ +#include <iprt/asn1-generator-init.h> + diff --git a/src/VBox/Runtime/common/crypto/tsp-internal.h b/src/VBox/Runtime/common/crypto/tsp-internal.h new file mode 100644 index 00000000..b07a5a1a --- /dev/null +++ b/src/VBox/Runtime/common/crypto/tsp-internal.h @@ -0,0 +1,47 @@ +/* $Id: tsp-internal.h $ */ +/** @file + * IPRT - Crypto - TSP, Internal Header. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef IPRT_INCLUDED_SRC_common_crypto_tsp_internal_h +#define IPRT_INCLUDED_SRC_common_crypto_tsp_internal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#define RTASN1TMPL_TEMPLATE_FILE "../common/crypto/tsp-template.h" +#include <iprt/asn1-generator-internal-header.h> + +#endif /* !IPRT_INCLUDED_SRC_common_crypto_tsp_internal_h */ + diff --git a/src/VBox/Runtime/common/crypto/tsp-sanity.cpp b/src/VBox/Runtime/common/crypto/tsp-sanity.cpp new file mode 100644 index 00000000..21579169 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/tsp-sanity.cpp @@ -0,0 +1,51 @@ +/* $Id: tsp-sanity.cpp $ */ +/** @file + * IPRT - Crypto - Time-Stamp Protocol (RFC-3161), Sanity Checkers. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/tsp.h> + +#include "tsp-internal.h" + + +/* + * Generate the code. + */ +#include <iprt/asn1-generator-sanity.h> + diff --git a/src/VBox/Runtime/common/crypto/tsp-template.h b/src/VBox/Runtime/common/crypto/tsp-template.h new file mode 100644 index 00000000..ca4beac6 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/tsp-template.h @@ -0,0 +1,113 @@ +/* $Id: tsp-template.h $ */ +/** @file + * IPRT - Crypto - Time-Stamp Protocol (RFC-3161), Code Generator Template. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#define RTASN1TMPL_DECL RTDECL + +/* + * MessageImprint + */ +#define RTASN1TMPL_TYPE RTCRTSPMESSAGEIMPRINT +#define RTASN1TMPL_EXT_NAME RTCrTspMessageImprint +#define RTASN1TMPL_INT_NAME rtCrTspMessageImprint +RTASN1TMPL_BEGIN_SEQCORE(); +RTASN1TMPL_MEMBER( HashAlgorithm, RTCRX509ALGORITHMIDENTIFIER, RTCrX509AlgorithmIdentifier); +RTASN1TMPL_MEMBER( HashedMessage, RTASN1OCTETSTRING, RTAsn1OctetString); +RTASN1TMPL_END_SEQCORE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + +/* + * TimeStampReq + */ + +/* + * PKIStatusInfo + */ + +/* + * TimeStampResp + */ + +/* + * Accuracy + * + * Note! Capping second accuracy at an hour to reduce chance exploiting this + * field to tinker with a signed structure. The RFC does not specify + * any upper limit. + * + * Note! Allowing a zero value for the 'millis' field because we've seen symantec + * return that when 'micros' is present. The RFC seems to want the TSA to + * omit the field if its value is zero. + */ +#define RTASN1TMPL_TYPE RTCRTSPACCURACY +#define RTASN1TMPL_EXT_NAME RTCrTspAccuracy +#define RTASN1TMPL_INT_NAME rtCrTspAccuracy +RTASN1TMPL_BEGIN_SEQCORE(); +RTASN1TMPL_MEMBER_OPT_ITAG_EX( Seconds, RTASN1INTEGER, RTAsn1Integer, ASN1_TAG_INTEGER, RTASN1TMPL_ITAG_F_UP, + RTASN1TMPL_MEMBER_CONSTR_U64_MIN_MAX(Seconds, 0, 3600, RT_NOTHING)); +RTASN1TMPL_MEMBER_OPT_ITAG_EX( Millis, RTASN1INTEGER, RTAsn1Integer, 0, RTASN1TMPL_ITAG_F_CP, + RTASN1TMPL_MEMBER_CONSTR_U64_MIN_MAX(Millis, 0, 999, RT_NOTHING)); +RTASN1TMPL_MEMBER_OPT_ITAG_EX( Micros, RTASN1INTEGER, RTAsn1Integer, 1, RTASN1TMPL_ITAG_F_CP, + RTASN1TMPL_MEMBER_CONSTR_U64_MIN_MAX(Micros, 1, 999, RT_NOTHING)); +RTASN1TMPL_END_SEQCORE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * TSTInfo + */ +#define RTASN1TMPL_TYPE RTCRTSPTSTINFO +#define RTASN1TMPL_EXT_NAME RTCrTspTstInfo +#define RTASN1TMPL_INT_NAME rtCrTspTstInfo +RTASN1TMPL_BEGIN_SEQCORE(); +RTASN1TMPL_MEMBER( Version, RTASN1INTEGER, RTAsn1Integer); +RTASN1TMPL_MEMBER( Policy, RTASN1OBJID, RTAsn1ObjId); +RTASN1TMPL_MEMBER( MessageImprint, RTCRTSPMESSAGEIMPRINT, RTCrTspMessageImprint); +RTASN1TMPL_MEMBER( SerialNumber, RTASN1INTEGER, RTAsn1Integer); +RTASN1TMPL_MEMBER( GenTime, RTASN1TIME, RTAsn1GeneralizedTime); +RTASN1TMPL_MEMBER_OPT_ITAG_UC( Accuracy, RTCRTSPACCURACY, RTCrTspAccuracy, ASN1_TAG_SEQUENCE); +RTASN1TMPL_MEMBER_DEF_ITAG_UP( Ordering, RTASN1BOOLEAN, RTAsn1Boolean, ASN1_TAG_BOOLEAN, 0 /*False*/); +RTASN1TMPL_MEMBER_OPT_ITAG_UP( Nonce, RTASN1INTEGER, RTAsn1Integer, ASN1_TAG_INTEGER); +RTASN1TMPL_MEMBER_OPT_XTAG( T0, CtxTag0, Tsa, RTCRX509GENERALNAME, RTCrX509GeneralName, 0); +RTASN1TMPL_MEMBER_OPT_ITAG( Extensions, RTCRX509EXTENSION, RTCrX509Extension, 1); +RTASN1TMPL_END_SEQCORE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + diff --git a/src/VBox/Runtime/common/crypto/x509-asn1-decoder.cpp b/src/VBox/Runtime/common/crypto/x509-asn1-decoder.cpp new file mode 100644 index 00000000..c2694612 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/x509-asn1-decoder.cpp @@ -0,0 +1,230 @@ +/* $Id: x509-asn1-decoder.cpp $ */ +/** @file + * IPRT - Crypto - X.509, Decoder for ASN.1. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/x509.h> + +#include <iprt/errcore.h> +#include <iprt/string.h> + +#include "x509-internal.h" + + +/* + * One X.509 Extension. + */ +RTDECL(int) RTCrX509Extension_ExtnValue_DecodeAsn1(PRTASN1CURSOR pCursor, uint32_t fFlags, + PRTCRX509EXTENSION pThis, const char *pszErrorTag) +{ + RT_NOREF_PV(fFlags); RT_NOREF_PV(pszErrorTag); + + pThis->enmValue = RTCRX509EXTENSIONVALUE_UNKNOWN; + + /* + * Decode the encapsulated extension bytes if know the format. + */ + RTASN1CURSOR ValueCursor; + int rc = RTAsn1CursorInitSubFromCore(pCursor, &pThis->ExtnValue.Asn1Core, &ValueCursor, "ExtnValue"); + if (RT_FAILURE(rc)) + return rc; + pCursor = &ValueCursor; + + if (RTAsn1ObjId_CompareWithString(&pThis->ExtnId, RTCRX509_ID_CE_AUTHORITY_KEY_IDENTIFIER_OID) == 0) + { + /* 4.2.1.1 Authority Key Identifier */ + PRTCRX509AUTHORITYKEYIDENTIFIER pThat; + rc = RTAsn1MemAllocZ(&pThis->ExtnValue.EncapsulatedAllocation, (void **)&pThat, sizeof(*pThat)); + if (RT_SUCCESS(rc)) + { + pThis->ExtnValue.pEncapsulated = &pThat->SeqCore.Asn1Core; + pThis->enmValue = RTCRX509EXTENSIONVALUE_AUTHORITY_KEY_IDENTIFIER; + rc = RTCrX509AuthorityKeyIdentifier_DecodeAsn1(&ValueCursor, 0, pThat, "AuthorityKeyIdentifier"); + } + } + else if (RTAsn1ObjId_CompareWithString(&pThis->ExtnId, RTCRX509_ID_CE_OLD_AUTHORITY_KEY_IDENTIFIER_OID) == 0) + { + /* Old and obsolete version of the above, still found in microsoft certificates. */ + PRTCRX509OLDAUTHORITYKEYIDENTIFIER pThat; + rc = RTAsn1MemAllocZ(&pThis->ExtnValue.EncapsulatedAllocation, (void **)&pThat, sizeof(*pThat)); + if (RT_SUCCESS(rc)) + { + pThis->ExtnValue.pEncapsulated = &pThat->SeqCore.Asn1Core; + pThis->enmValue = RTCRX509EXTENSIONVALUE_OLD_AUTHORITY_KEY_IDENTIFIER; + rc = RTCrX509OldAuthorityKeyIdentifier_DecodeAsn1(&ValueCursor, 0, pThat, "OldAuthorityKeyIdentifier"); + } + } + else if (RTAsn1ObjId_CompareWithString(&pThis->ExtnId, RTCRX509_ID_CE_SUBJECT_KEY_IDENTIFIER_OID) == 0) + { + /* 4.2.1.2 Subject Key Identifier */ + PRTASN1OCTETSTRING pThat; + rc = RTAsn1MemAllocZ(&pThis->ExtnValue.EncapsulatedAllocation, (void **)&pThat, sizeof(*pThat)); + if (RT_SUCCESS(rc)) + { + pThis->ExtnValue.pEncapsulated = &pThat->Asn1Core; + pThis->enmValue = RTCRX509EXTENSIONVALUE_OCTET_STRING; + rc = RTAsn1CursorGetOctetString(&ValueCursor, 0, pThat, "SubjectKeyIdentifier"); + } + } + else if (RTAsn1ObjId_CompareWithString(&pThis->ExtnId, RTCRX509_ID_CE_KEY_USAGE_OID) == 0) + { + /* 4.2.1.3 Key Usage */ + PRTASN1BITSTRING pThat; + rc = RTAsn1MemAllocZ(&pThis->ExtnValue.EncapsulatedAllocation, (void **)&pThat, sizeof(*pThat)); + if (RT_SUCCESS(rc)) + { + pThis->ExtnValue.pEncapsulated = &pThat->Asn1Core; + pThis->enmValue = RTCRX509EXTENSIONVALUE_BIT_STRING; + rc = RTAsn1CursorGetBitStringEx(&ValueCursor, 0, 9, pThat, "KeyUsage"); + } + } + else if (RTAsn1ObjId_CompareWithString(&pThis->ExtnId, RTCRX509_ID_CE_CERTIFICATE_POLICIES_OID) == 0) + { + /* 4.2.1.4 Certificate Policies */ + PRTCRX509CERTIFICATEPOLICIES pThat; + rc = RTAsn1MemAllocZ(&pThis->ExtnValue.EncapsulatedAllocation, (void **)&pThat, sizeof(*pThat)); + if (RT_SUCCESS(rc)) + { + pThis->ExtnValue.pEncapsulated = &pThat->SeqCore.Asn1Core; + pThis->enmValue = RTCRX509EXTENSIONVALUE_CERTIFICATE_POLICIES; + rc = RTCrX509CertificatePolicies_DecodeAsn1(&ValueCursor, 0, pThat, "CertPolicies"); + } + } + else if (RTAsn1ObjId_CompareWithString(&pThis->ExtnId, RTCRX509_ID_CE_POLICY_MAPPINGS_OID) == 0) + { + /* 4.2.1.5 Policy Mappings */ + PRTCRX509POLICYMAPPINGS pThat; + rc = RTAsn1MemAllocZ(&pThis->ExtnValue.EncapsulatedAllocation, (void **)&pThat, sizeof(*pThat)); + if (RT_SUCCESS(rc)) + { + pThis->ExtnValue.pEncapsulated = &pThat->SeqCore.Asn1Core; + pThis->enmValue = RTCRX509EXTENSIONVALUE_POLICY_MAPPINGS; + rc = RTCrX509PolicyMappings_DecodeAsn1(&ValueCursor, 0, pThat, "PolicyMapppings"); + } + } + else if ( RTAsn1ObjId_CompareWithString(&pThis->ExtnId, RTCRX509_ID_CE_SUBJECT_ALT_NAME_OID) == 0 + || RTAsn1ObjId_CompareWithString(&pThis->ExtnId, RTCRX509_ID_CE_ISSUER_ALT_NAME_OID) == 0) + { + /* 4.2.1.6 Subject Alternative Name / 4.2.1.7 Issuer Alternative Name */ + PRTCRX509GENERALNAMES pThat; + rc = RTAsn1MemAllocZ(&pThis->ExtnValue.EncapsulatedAllocation, (void **)&pThat, sizeof(*pThat)); + if (RT_SUCCESS(rc)) + { + pThis->ExtnValue.pEncapsulated = &pThat->SeqCore.Asn1Core; + pThis->enmValue = RTCRX509EXTENSIONVALUE_GENERAL_NAMES; + rc = RTCrX509GeneralNames_DecodeAsn1(&ValueCursor, 0, pThat, "AltName"); + } + } + else if (RTAsn1ObjId_CompareWithString(&pThis->ExtnId, RTCRX509_ID_CE_BASIC_CONSTRAINTS_OID) == 0) + { + /* 4.2.1.9 Basic Constraints */ + PRTCRX509BASICCONSTRAINTS pThat; + rc = RTAsn1MemAllocZ(&pThis->ExtnValue.EncapsulatedAllocation, (void **)&pThat, sizeof(*pThat)); + if (RT_SUCCESS(rc)) + { + pThis->ExtnValue.pEncapsulated = &pThat->SeqCore.Asn1Core; + pThis->enmValue = RTCRX509EXTENSIONVALUE_BASIC_CONSTRAINTS; + rc = RTCrX509BasicConstraints_DecodeAsn1(&ValueCursor, 0, pThat, "BasicConstraints"); + } + } + else if (RTAsn1ObjId_CompareWithString(&pThis->ExtnId, RTCRX509_ID_CE_NAME_CONSTRAINTS_OID) == 0) + { + /* 4.2.1.10 Name Constraints */ + PRTCRX509NAMECONSTRAINTS pThat; + rc = RTAsn1MemAllocZ(&pThis->ExtnValue.EncapsulatedAllocation, (void **)&pThat, sizeof(*pThat)); + if (RT_SUCCESS(rc)) + { + pThis->ExtnValue.pEncapsulated = &pThat->SeqCore.Asn1Core; + pThis->enmValue = RTCRX509EXTENSIONVALUE_NAME_CONSTRAINTS; + rc = RTCrX509NameConstraints_DecodeAsn1(&ValueCursor, 0, pThat, "NameConstraints"); + } + } + else if (RTAsn1ObjId_CompareWithString(&pThis->ExtnId, RTCRX509_ID_CE_POLICY_CONSTRAINTS_OID) == 0) + { + /* 4.2.1.11 Policy Constraints */ + PRTCRX509POLICYCONSTRAINTS pThat; + rc = RTAsn1MemAllocZ(&pThis->ExtnValue.EncapsulatedAllocation, (void **)&pThat, sizeof(*pThat)); + if (RT_SUCCESS(rc)) + { + pThis->ExtnValue.pEncapsulated = &pThat->SeqCore.Asn1Core; + pThis->enmValue = RTCRX509EXTENSIONVALUE_POLICY_CONSTRAINTS; + rc = RTCrX509PolicyConstraints_DecodeAsn1(&ValueCursor, 0, pThat, "PolicyConstraints"); + } + } + else if (RTAsn1ObjId_CompareWithString(&pThis->ExtnId, RTCRX509_ID_CE_EXT_KEY_USAGE_OID) == 0) + { + /* 4.2.1.12 Extended Key Usage */ + PRTASN1SEQOFOBJIDS pThat; + rc = RTAsn1MemAllocZ(&pThis->ExtnValue.EncapsulatedAllocation, (void **)&pThat, sizeof(*pThat)); + if (RT_SUCCESS(rc)) + { + pThis->ExtnValue.pEncapsulated = &pThat->SeqCore.Asn1Core; + pThis->enmValue = RTCRX509EXTENSIONVALUE_SEQ_OF_OBJ_IDS; + rc = RTAsn1SeqOfObjIds_DecodeAsn1(&ValueCursor, 0, pThat, "ExKeyUsage"); + } + } + else if (RTAsn1ObjId_CompareWithString(&pThis->ExtnId, RTCRX509_ID_CE_EXT_KEY_USAGE_OID) == 0) + { + /* 4.2.1.14 Inhibit anyPolicy */ + PRTASN1INTEGER pThat; + rc = RTAsn1MemAllocZ(&pThis->ExtnValue.EncapsulatedAllocation, (void **)&pThat, sizeof(*pThat)); + if (RT_SUCCESS(rc)) + { + pThis->ExtnValue.pEncapsulated = &pThat->Asn1Core; + pThis->enmValue = RTCRX509EXTENSIONVALUE_INTEGER; + rc = RTAsn1CursorGetInteger(&ValueCursor, 0, pThat, "InhibitAnyPolicy"); + } + } + else + return VINF_SUCCESS; + + if (RT_SUCCESS(rc)) + rc = RTAsn1CursorCheckEnd(&ValueCursor); + + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + return rc; +} + + +/* + * Generate the code. + */ +#include <iprt/asn1-generator-asn1-decoder.h> + diff --git a/src/VBox/Runtime/common/crypto/x509-certpaths.cpp b/src/VBox/Runtime/common/crypto/x509-certpaths.cpp new file mode 100644 index 00000000..4b65a50c --- /dev/null +++ b/src/VBox/Runtime/common/crypto/x509-certpaths.cpp @@ -0,0 +1,3017 @@ +/* $Id: x509-certpaths.cpp $ */ +/** @file + * IPRT - Crypto - X.509, Simple Certificate Path Builder & Validator. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_CRYPTO +#include "internal/iprt.h" +#include <iprt/crypto/x509.h> + +#include <iprt/asm.h> +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/list.h> +#include <iprt/log.h> +#include <iprt/time.h> +#include <iprt/crypto/applecodesign.h> /* critical extension OIDs */ +#include <iprt/crypto/pkcs7.h> /* PCRTCRPKCS7SETOFCERTS */ +#include <iprt/crypto/store.h> + +#include "x509-internal.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * X.509 certificate path node. + */ +typedef struct RTCRX509CERTPATHNODE +{ + /** Sibling list entry. */ + RTLISTNODE SiblingEntry; + /** List of children or leaf list entry. */ + RTLISTANCHOR ChildListOrLeafEntry; + /** Pointer to the parent node. NULL for root. */ + struct RTCRX509CERTPATHNODE *pParent; + + /** The distance between this node and the target. */ + uint32_t uDepth : 8; + /** Indicates the source of this certificate. */ + uint32_t uSrc : 3; + /** Set if this is a leaf node. */ + uint32_t fLeaf : 1; + /** Makes sure it's a 32-bit bitfield. */ + uint32_t uReserved : 20; + + /** Leaf only: The result of the last path vertification. */ + int rcVerify; + + /** Pointer to the certificate. This can be NULL only for trust anchors. */ + PCRTCRX509CERTIFICATE pCert; + + /** If the certificate or trust anchor was obtained from a store, this is the + * associated certificate context (referenced of course). This is used to + * access the trust anchor information, if present. + * + * (If this is NULL it's from a certificate array or some such given directly to + * the path building code. It's assumed the caller doesn't free these until the + * path validation/whatever is done with and the paths destroyed.) */ + PCRTCRCERTCTX pCertCtx; +} RTCRX509CERTPATHNODE; +/** Pointer to a X.509 path node. */ +typedef RTCRX509CERTPATHNODE *PRTCRX509CERTPATHNODE; + +/** @name RTCRX509CERTPATHNODE::uSrc values. + * The trusted and untrusted sources ordered in priority order, where higher + * number means high priority in case of duplicates. + * @{ */ +#define RTCRX509CERTPATHNODE_SRC_NONE 0 +#define RTCRX509CERTPATHNODE_SRC_TARGET 1 +#define RTCRX509CERTPATHNODE_SRC_UNTRUSTED_SET 2 +#define RTCRX509CERTPATHNODE_SRC_UNTRUSTED_ARRAY 3 +#define RTCRX509CERTPATHNODE_SRC_UNTRUSTED_STORE 4 +#define RTCRX509CERTPATHNODE_SRC_TRUSTED_STORE 5 +#define RTCRX509CERTPATHNODE_SRC_TRUSTED_CERT 6 +#define RTCRX509CERTPATHNODE_SRC_IS_TRUSTED(uSrc) ((uSrc) >= RTCRX509CERTPATHNODE_SRC_TRUSTED_STORE) +/** @} */ + + +/** + * Policy tree node. + */ +typedef struct RTCRX509CERTPATHSPOLICYNODE +{ + /** Sibling list entry. */ + RTLISTNODE SiblingEntry; + /** Tree depth list entry. */ + RTLISTNODE DepthEntry; + /** List of children or leaf list entry. */ + RTLISTANCHOR ChildList; + /** Pointer to the parent. */ + struct RTCRX509CERTPATHSPOLICYNODE *pParent; + + /** The policy object ID. */ + PCRTASN1OBJID pValidPolicy; + + /** Optional sequence of policy qualifiers. */ + PCRTCRX509POLICYQUALIFIERINFOS pPolicyQualifiers; + + /** The first policy ID in the exepcted policy set. */ + PCRTASN1OBJID pExpectedPolicyFirst; + /** Set if we've already mapped pExpectedPolicyFirst. */ + bool fAlreadyMapped; + /** Number of additional items in the expected policy set. */ + uint32_t cMoreExpectedPolicySet; + /** Additional items in the expected policy set. */ + PCRTASN1OBJID *papMoreExpectedPolicySet; +} RTCRX509CERTPATHSPOLICYNODE; +/** Pointer to a policy tree node. */ +typedef RTCRX509CERTPATHSPOLICYNODE *PRTCRX509CERTPATHSPOLICYNODE; + + +/** + * Path builder and validator instance. + * + * The path builder creates a tree of certificates by forward searching from the + * end-entity towards a trusted source. The leaf nodes are inserted into list + * ordered by the source of the leaf certificate and the path length (i.e. tree + * depth). + * + * The path validator works the tree from the leaf end and validates each + * potential path found by the builder. It is generally happy with one working + * path, but may be told to verify all of them. + */ +typedef struct RTCRX509CERTPATHSINT +{ + /** Magic number. */ + uint32_t u32Magic; + /** Reference counter. */ + uint32_t volatile cRefs; + + /** @name Input + * @{ */ + /** The target certificate (end entity) to build a trusted path for. */ + PCRTCRX509CERTIFICATE pTarget; + + /** Lone trusted certificate. */ + PCRTCRX509CERTIFICATE pTrustedCert; + /** Store of trusted certificates. */ + RTCRSTORE hTrustedStore; + + /** Store of untrusted certificates. */ + RTCRSTORE hUntrustedStore; + /** Array of untrusted certificates, typically from the protocol. */ + PCRTCRX509CERTIFICATE paUntrustedCerts; + /** Number of entries in paUntrusted. */ + uint32_t cUntrustedCerts; + /** Set of untrusted PKCS \#7 / CMS certificatess. */ + PCRTCRPKCS7SETOFCERTS pUntrustedCertsSet; + + /** UTC time we're going to validate the path at, requires + * RTCRX509CERTPATHSINT_F_VALID_TIME to be set. */ + RTTIMESPEC ValidTime; + /** Number of policy OIDs in the user initial policy set, 0 means anyPolicy. */ + uint32_t cInitialUserPolicySet; + /** The user initial policy set. As with all other user provided data, we + * assume it's immutable and remains valid for the usage period of the path + * builder & validator. */ + PCRTASN1OBJID *papInitialUserPolicySet; + /** Number of certificates before the user wants an explicit policy result. + * Set to UINT32_MAX no explicit policy restriction required by the user. */ + uint32_t cInitialExplicitPolicy; + /** Number of certificates before the user wants policy mapping to be + * inhibited. Set to UINT32_MAX if no initial policy mapping inhibition + * desired by the user. */ + uint32_t cInitialPolicyMappingInhibit; + /** Number of certificates before the user wants the anyPolicy to be rejected. + * Set to UINT32_MAX no explicit policy restriction required by the user. */ + uint32_t cInitialInhibitAnyPolicy; + /** Initial name restriction: Permitted subtrees. */ + PCRTCRX509GENERALSUBTREES pInitialPermittedSubtrees; + /** Initial name restriction: Excluded subtrees. */ + PCRTCRX509GENERALSUBTREES pInitialExcludedSubtrees; + + /** Flags RTCRX509CERTPATHSINT_F_XXX. */ + uint32_t fFlags; + /** @} */ + + /** Sticky status for remembering allocation errors and the like. */ + int32_t rc; + /** Where to store extended error info (optional). */ + PRTERRINFO pErrInfo; + + /** @name Path Builder Output + * @{ */ + /** Pointer to the root of the tree. This will always be non-NULL after path + * building and thus can be reliably used to tell if path building has taken + * place or not. */ + PRTCRX509CERTPATHNODE pRoot; + /** List of working leaf tree nodes. */ + RTLISTANCHOR LeafList; + /** The number of paths (leafs). */ + uint32_t cPaths; + /** @} */ + + /** Path Validator State. */ + struct + { + /** Number of nodes in the certificate path we're validating (aka 'n'). */ + uint32_t cNodes; + /** The current node (0 being the trust anchor). */ + uint32_t iNode; + + /** The root node of the valid policy tree. */ + PRTCRX509CERTPATHSPOLICYNODE pValidPolicyTree; + /** An array of length cNodes + 1 which tracks all nodes at the given (index) + * tree depth via the RTCRX509CERTPATHSPOLICYNODE::DepthEntry member. */ + PRTLISTANCHOR paValidPolicyDepthLists; + + /** Number of entries in paPermittedSubtrees (name constraints). + * If zero, no permitted name constrains currently in effect. */ + uint32_t cPermittedSubtrees; + /** The allocated size of papExcludedSubtrees */ + uint32_t cPermittedSubtreesAlloc; + /** Array of permitted subtrees we've collected so far (name constraints). */ + PCRTCRX509GENERALSUBTREE *papPermittedSubtrees; + /** Set if we end up with an empty set after calculating a name constraints + * union. */ + bool fNoPermittedSubtrees; + + /** Number of entries in paExcludedSubtrees (name constraints). + * If zero, no excluded name constrains currently in effect. */ + uint32_t cExcludedSubtrees; + /** Array of excluded subtrees we've collected so far (name constraints). */ + PCRTCRX509GENERALSUBTREES *papExcludedSubtrees; + + /** Number of non-self-issued certificates to be processed before a non-NULL + * paValidPolicyTree is required. */ + uint32_t cExplicitPolicy; + /** Number of non-self-issued certificates to be processed we stop processing + * policy mapping extensions. */ + uint32_t cInhibitPolicyMapping; + /** Number of non-self-issued certificates to be processed before a the + * anyPolicy is rejected. */ + uint32_t cInhibitAnyPolicy; + /** Number of non-self-issued certificates we're allowed to process. */ + uint32_t cMaxPathLength; + + /** The working issuer name. */ + PCRTCRX509NAME pWorkingIssuer; + /** The working public key algorithm ID. */ + PCRTASN1OBJID pWorkingPublicKeyAlgorithm; + /** The working public key algorithm parameters. */ + PCRTASN1DYNTYPE pWorkingPublicKeyParameters; + /** A bit string containing the public key. */ + PCRTASN1BITSTRING pWorkingPublicKey; + } v; + + /** An object identifier initialized to anyPolicy. */ + RTASN1OBJID AnyPolicyObjId; + + /** Temporary scratch space. */ + char szTmp[1024]; +} RTCRX509CERTPATHSINT; +typedef RTCRX509CERTPATHSINT *PRTCRX509CERTPATHSINT; + +/** Magic value for RTCRX509CERTPATHSINT::u32Magic (Bruce Schneier). */ +#define RTCRX509CERTPATHSINT_MAGIC UINT32_C(0x19630115) + +/** @name RTCRX509CERTPATHSINT_F_XXX - Certificate path build flags. + * @{ */ +#define RTCRX509CERTPATHSINT_F_VALID_TIME RT_BIT_32(0) +#define RTCRX509CERTPATHSINT_F_ELIMINATE_UNTRUSTED_PATHS RT_BIT_32(1) +/** Whether checking the trust anchor signature (if self signed) and + * that it is valid at the verification time, also require it to be a CA if not + * leaf node. */ +#define RTCRX509CERTPATHSINT_F_CHECK_TRUST_ANCHOR RT_BIT_32(2) +#define RTCRX509CERTPATHSINT_F_VALID_MASK UINT32_C(0x00000007) +/** @} */ + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static void rtCrX509CertPathsDestroyTree(PRTCRX509CERTPATHSINT pThis); +static void rtCrX509CpvCleanup(PRTCRX509CERTPATHSINT pThis); + + +/** @name Path Builder and Validator Config APIs + * @{ + */ + +RTDECL(int) RTCrX509CertPathsCreate(PRTCRX509CERTPATHS phCertPaths, PCRTCRX509CERTIFICATE pTarget) +{ + AssertPtrReturn(phCertPaths, VERR_INVALID_POINTER); + + PRTCRX509CERTPATHSINT pThis = (PRTCRX509CERTPATHSINT)RTMemAllocZ(sizeof(*pThis)); + if (pThis) + { + int rc = RTAsn1ObjId_InitFromString(&pThis->AnyPolicyObjId, RTCRX509_ID_CE_CP_ANY_POLICY_OID, &g_RTAsn1DefaultAllocator); + if (RT_SUCCESS(rc)) + { + pThis->u32Magic = RTCRX509CERTPATHSINT_MAGIC; + pThis->cRefs = 1; + pThis->pTarget = pTarget; + pThis->hTrustedStore = NIL_RTCRSTORE; + pThis->hUntrustedStore = NIL_RTCRSTORE; + pThis->cInitialExplicitPolicy = UINT32_MAX; + pThis->cInitialPolicyMappingInhibit = UINT32_MAX; + pThis->cInitialInhibitAnyPolicy = UINT32_MAX; + pThis->rc = VINF_SUCCESS; + RTListInit(&pThis->LeafList); + *phCertPaths = pThis; + return VINF_SUCCESS; + } + return rc; + } + return VERR_NO_MEMORY; +} + + +RTDECL(uint32_t) RTCrX509CertPathsRetain(RTCRX509CERTPATHS hCertPaths) +{ + PRTCRX509CERTPATHSINT pThis = hCertPaths; + AssertPtrReturn(pThis, UINT32_MAX); + + uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs); + Assert(cRefs > 0 && cRefs < 64); + return cRefs; +} + + +RTDECL(uint32_t) RTCrX509CertPathsRelease(RTCRX509CERTPATHS hCertPaths) +{ + uint32_t cRefs; + if (hCertPaths != NIL_RTCRX509CERTPATHS) + { + PRTCRX509CERTPATHSINT pThis = hCertPaths; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTCRX509CERTPATHSINT_MAGIC, UINT32_MAX); + + cRefs = ASMAtomicDecU32(&pThis->cRefs); + Assert(cRefs < 64); + if (!cRefs) + { + /* + * No more references, destroy the whole thing. + */ + ASMAtomicWriteU32(&pThis->u32Magic, ~RTCRX509CERTPATHSINT_MAGIC); + + /* config */ + pThis->pTarget = NULL; /* Referencing user memory. */ + pThis->pTrustedCert = NULL; /* Referencing user memory. */ + RTCrStoreRelease(pThis->hTrustedStore); + pThis->hTrustedStore = NIL_RTCRSTORE; + RTCrStoreRelease(pThis->hUntrustedStore); + pThis->hUntrustedStore = NIL_RTCRSTORE; + pThis->paUntrustedCerts = NULL; /* Referencing user memory. */ + pThis->pUntrustedCertsSet = NULL; /* Referencing user memory. */ + pThis->papInitialUserPolicySet = NULL; /* Referencing user memory. */ + pThis->pInitialPermittedSubtrees = NULL; /* Referencing user memory. */ + pThis->pInitialExcludedSubtrees = NULL; /* Referencing user memory. */ + + /* builder */ + rtCrX509CertPathsDestroyTree(pThis); + + /* validator */ + rtCrX509CpvCleanup(pThis); + + /* misc */ + RTAsn1VtDelete(&pThis->AnyPolicyObjId.Asn1Core); + + /* Finally, the instance itself. */ + RTMemFree(pThis); + } + } + else + cRefs = 0; + return cRefs; +} + + + +RTDECL(int) RTCrX509CertPathsSetTrustedStore(RTCRX509CERTPATHS hCertPaths, RTCRSTORE hTrustedStore) +{ + PRTCRX509CERTPATHSINT pThis = hCertPaths; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRX509CERTPATHSINT_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(pThis->pRoot == NULL, VERR_WRONG_ORDER); + + if (pThis->hTrustedStore != NIL_RTCRSTORE) + { + RTCrStoreRelease(pThis->hTrustedStore); + pThis->hTrustedStore = NIL_RTCRSTORE; + } + if (hTrustedStore != NIL_RTCRSTORE) + { + AssertReturn(RTCrStoreRetain(hTrustedStore) != UINT32_MAX, VERR_INVALID_HANDLE); + pThis->hTrustedStore = hTrustedStore; + } + return VINF_SUCCESS; +} + + +RTDECL(int) RTCrX509CertPathsSetUntrustedStore(RTCRX509CERTPATHS hCertPaths, RTCRSTORE hUntrustedStore) +{ + PRTCRX509CERTPATHSINT pThis = hCertPaths; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRX509CERTPATHSINT_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(pThis->pRoot == NULL, VERR_WRONG_ORDER); + + if (pThis->hUntrustedStore != NIL_RTCRSTORE) + { + RTCrStoreRelease(pThis->hUntrustedStore); + pThis->hUntrustedStore = NIL_RTCRSTORE; + } + if (hUntrustedStore != NIL_RTCRSTORE) + { + AssertReturn(RTCrStoreRetain(hUntrustedStore) != UINT32_MAX, VERR_INVALID_HANDLE); + pThis->hUntrustedStore = hUntrustedStore; + } + return VINF_SUCCESS; +} + + +RTDECL(int) RTCrX509CertPathsSetUntrustedArray(RTCRX509CERTPATHS hCertPaths, PCRTCRX509CERTIFICATE paCerts, uint32_t cCerts) +{ + PRTCRX509CERTPATHSINT pThis = hCertPaths; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRX509CERTPATHSINT_MAGIC, VERR_INVALID_HANDLE); + + pThis->paUntrustedCerts = paCerts; + pThis->cUntrustedCerts = cCerts; + return VINF_SUCCESS; +} + + +RTDECL(int) RTCrX509CertPathsSetUntrustedSet(RTCRX509CERTPATHS hCertPaths, PCRTCRPKCS7SETOFCERTS pSetOfCerts) +{ + PRTCRX509CERTPATHSINT pThis = hCertPaths; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRX509CERTPATHSINT_MAGIC, VERR_INVALID_HANDLE); + + pThis->pUntrustedCertsSet = pSetOfCerts; + return VINF_SUCCESS; +} + + +RTDECL(int) RTCrX509CertPathsSetValidTime(RTCRX509CERTPATHS hCertPaths, PCRTTIME pTime) +{ + PRTCRX509CERTPATHSINT pThis = hCertPaths; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRX509CERTPATHSINT_MAGIC, VERR_INVALID_HANDLE); + + /* Allow this after building paths, as it's only used during verification. */ + + if (pTime) + { + if (RTTimeImplode(&pThis->ValidTime, pTime)) + return VERR_INVALID_PARAMETER; + pThis->fFlags |= RTCRX509CERTPATHSINT_F_VALID_TIME; + } + else + pThis->fFlags &= ~RTCRX509CERTPATHSINT_F_VALID_TIME; + return VINF_SUCCESS; +} + + +RTDECL(int) RTCrX509CertPathsSetValidTimeSpec(RTCRX509CERTPATHS hCertPaths, PCRTTIMESPEC pTimeSpec) +{ + PRTCRX509CERTPATHSINT pThis = hCertPaths; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRX509CERTPATHSINT_MAGIC, VERR_INVALID_HANDLE); + + /* Allow this after building paths, as it's only used during verification. */ + + if (pTimeSpec) + { + pThis->ValidTime = *pTimeSpec; + pThis->fFlags |= RTCRX509CERTPATHSINT_F_VALID_TIME; + } + else + pThis->fFlags &= ~RTCRX509CERTPATHSINT_F_VALID_TIME; + return VINF_SUCCESS; +} + + +RTDECL(int) RTCrX509CertPathsSetTrustAnchorChecks(RTCRX509CERTPATHS hCertPaths, bool fEnable) +{ + PRTCRX509CERTPATHSINT pThis = hCertPaths; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRX509CERTPATHSINT_MAGIC, VERR_INVALID_HANDLE); + + if (fEnable) + pThis->fFlags |= RTCRX509CERTPATHSINT_F_CHECK_TRUST_ANCHOR; + else + pThis->fFlags &= ~RTCRX509CERTPATHSINT_F_CHECK_TRUST_ANCHOR; + return VINF_SUCCESS; +} + + +RTDECL(int) RTCrX509CertPathsCreateEx(PRTCRX509CERTPATHS phCertPaths, PCRTCRX509CERTIFICATE pTarget, RTCRSTORE hTrustedStore, + RTCRSTORE hUntrustedStore, PCRTCRX509CERTIFICATE paUntrustedCerts, uint32_t cUntrustedCerts, + PCRTTIMESPEC pValidTime) +{ + int rc = RTCrX509CertPathsCreate(phCertPaths, pTarget); + if (RT_SUCCESS(rc)) + { + PRTCRX509CERTPATHSINT pThis = *phCertPaths; + + rc = RTCrX509CertPathsSetTrustedStore(pThis, hTrustedStore); + if (RT_SUCCESS(rc)) + { + rc = RTCrX509CertPathsSetUntrustedStore(pThis, hUntrustedStore); + if (RT_SUCCESS(rc)) + { + rc = RTCrX509CertPathsSetUntrustedArray(pThis, paUntrustedCerts, cUntrustedCerts); + if (RT_SUCCESS(rc)) + { + rc = RTCrX509CertPathsSetValidTimeSpec(pThis, pValidTime); + if (RT_SUCCESS(rc)) + { + return VINF_SUCCESS; + } + } + RTCrStoreRelease(pThis->hUntrustedStore); + } + RTCrStoreRelease(pThis->hTrustedStore); + } + RTMemFree(pThis); + *phCertPaths = NIL_RTCRX509CERTPATHS; + } + return rc; +} + +/** @} */ + + + +/** @name Path Builder and Validator Common Utility Functions. + * @{ + */ + +/** + * Checks if the certificate is self-issued. + * + * @returns true / false. + * @param pNode The path node to check.. + */ +static bool rtCrX509CertPathsIsSelfIssued(PRTCRX509CERTPATHNODE pNode) +{ + return pNode->pCert + && RTCrX509Name_MatchByRfc5280(&pNode->pCert->TbsCertificate.Subject, &pNode->pCert->TbsCertificate.Issuer); +} + +/** + * Helper for checking whether a certificate is in the trusted store or not. + */ +static bool rtCrX509CertPathsIsCertInStore(PRTCRX509CERTPATHNODE pNode, RTCRSTORE hStore) +{ + bool fRc = false; + PCRTCRCERTCTX pCertCtx = RTCrStoreCertByIssuerAndSerialNo(hStore, &pNode->pCert->TbsCertificate.Issuer, + &pNode->pCert->TbsCertificate.SerialNumber); + if (pCertCtx) + { + if (pCertCtx->pCert) + fRc = RTCrX509Certificate_Compare(pCertCtx->pCert, pNode->pCert) == 0; + RTCrCertCtxRelease(pCertCtx); + } + return fRc; +} + +/** @} */ + + + +/** @name Path Builder Functions. + * @{ + */ + +static PRTCRX509CERTPATHNODE rtCrX509CertPathsNewNode(PRTCRX509CERTPATHSINT pThis) +{ + PRTCRX509CERTPATHNODE pNode = (PRTCRX509CERTPATHNODE)RTMemAllocZ(sizeof(*pNode)); + if (RT_LIKELY(pNode)) + { + RTListInit(&pNode->SiblingEntry); + RTListInit(&pNode->ChildListOrLeafEntry); + pNode->rcVerify = VERR_CR_X509_NOT_VERIFIED; + + return pNode; + } + + pThis->rc = RTErrInfoSet(pThis->pErrInfo, VERR_NO_MEMORY, "No memory for path node"); + return NULL; +} + + +static void rtCrX509CertPathsDestroyNode(PRTCRX509CERTPATHNODE pNode) +{ + if (pNode->pCertCtx) + { + RTCrCertCtxRelease(pNode->pCertCtx); + pNode->pCertCtx = NULL; + } + RT_ZERO(*pNode); + RTMemFree(pNode); +} + + +static void rtCrX509CertPathsAddIssuer(PRTCRX509CERTPATHSINT pThis, PRTCRX509CERTPATHNODE pParent, + PCRTCRX509CERTIFICATE pCert, PCRTCRCERTCTX pCertCtx, uint8_t uSrc) +{ + /* + * Check if we've seen this certificate already in the current path or + * among the already gathered issuers. + */ + if (pCert) + { + /* No duplicate certificates in the path. */ + PRTCRX509CERTPATHNODE pTmpNode = pParent; + while (pTmpNode) + { + Assert(pTmpNode->pCert); + if ( pTmpNode->pCert == pCert + || RTCrX509Certificate_Compare(pTmpNode->pCert, pCert) == 0) + { + /* If target and the source it trusted, upgrade the source so we can successfully verify single node 'paths'. */ + if ( RTCRX509CERTPATHNODE_SRC_IS_TRUSTED(uSrc) + && pTmpNode == pParent + && pTmpNode->uSrc == RTCRX509CERTPATHNODE_SRC_TARGET) + { + AssertReturnVoid(!pTmpNode->pParent); + pTmpNode->uSrc = uSrc; + } + return; + } + pTmpNode = pTmpNode->pParent; + } + + /* No duplicate tree branches. */ + RTListForEach(&pParent->ChildListOrLeafEntry, pTmpNode, RTCRX509CERTPATHNODE, SiblingEntry) + { + if (RTCrX509Certificate_Compare(pTmpNode->pCert, pCert) == 0) + return; + } + } + else + Assert(pCertCtx); + + /* + * Reference the context core before making the allocation. + */ + if (pCertCtx) + AssertReturnVoidStmt(RTCrCertCtxRetain(pCertCtx) != UINT32_MAX, + pThis->rc = RTErrInfoSetF(pThis->pErrInfo, VERR_CR_X509_CPB_BAD_CERT_CTX, + "Bad pCertCtx=%p", pCertCtx)); + + /* + * We haven't see it, append it as a child. + */ + PRTCRX509CERTPATHNODE pNew = rtCrX509CertPathsNewNode(pThis); + if (pNew) + { + pNew->pParent = pParent; + pNew->pCert = pCert; + pNew->pCertCtx = pCertCtx; + pNew->uSrc = uSrc; + pNew->uDepth = pParent->uDepth + 1; + RTListAppend(&pParent->ChildListOrLeafEntry, &pNew->SiblingEntry); + Log2Func(("pNew=%p uSrc=%u uDepth=%u\n", pNew, uSrc, pNew->uDepth)); + } + else + RTCrCertCtxRelease(pCertCtx); +} + + +static void rtCrX509CertPathsGetIssuersFromStore(PRTCRX509CERTPATHSINT pThis, PRTCRX509CERTPATHNODE pNode, + PCRTCRX509NAME pIssuer, RTCRSTORE hStore, uint8_t uSrc) +{ + RTCRSTORECERTSEARCH Search; + int rc = RTCrStoreCertFindBySubjectOrAltSubjectByRfc5280(hStore, pIssuer, &Search); + if (RT_SUCCESS(rc)) + { + PCRTCRCERTCTX pCertCtx; + while ((pCertCtx = RTCrStoreCertSearchNext(hStore, &Search)) != NULL) + { + if ( pCertCtx->pCert + || ( RTCRX509CERTPATHNODE_SRC_IS_TRUSTED(uSrc) + && pCertCtx->pTaInfo) ) + rtCrX509CertPathsAddIssuer(pThis, pNode, pCertCtx->pCert, pCertCtx, uSrc); + RTCrCertCtxRelease(pCertCtx); + } + RTCrStoreCertSearchDestroy(hStore, &Search); + } +} + + +static void rtCrX509CertPathsGetIssuers(PRTCRX509CERTPATHSINT pThis, PRTCRX509CERTPATHNODE pNode) +{ + Assert(RTListIsEmpty(&pNode->ChildListOrLeafEntry)); + Assert(!pNode->fLeaf); + Assert(pNode->pCert); + + /* + * Don't recurse infintely. + */ + if (RT_UNLIKELY(pNode->uDepth >= 50)) + return; + + PCRTCRX509NAME const pIssuer = &pNode->pCert->TbsCertificate.Issuer; +#if defined(LOG_ENABLED) && defined(IN_RING3) + if (LogIs2Enabled()) + { + char szIssuer[128] = {0}; + RTCrX509Name_FormatAsString(pIssuer, szIssuer, sizeof(szIssuer), NULL); + char szSubject[128] = {0}; + RTCrX509Name_FormatAsString(&pNode->pCert->TbsCertificate.Subject, szSubject, sizeof(szSubject), NULL); + Log2Func(("pNode=%p uSrc=%u uDepth=%u Issuer='%s' (Subject='%s')\n", pNode, pNode->uSrc, pNode->uDepth, szIssuer, szSubject)); + } +#endif + + /* + * Trusted certificate. + */ + if ( pThis->pTrustedCert + && RTCrX509Certificate_MatchSubjectOrAltSubjectByRfc5280(pThis->pTrustedCert, pIssuer)) + rtCrX509CertPathsAddIssuer(pThis, pNode, pThis->pTrustedCert, NULL, RTCRX509CERTPATHNODE_SRC_TRUSTED_CERT); + + /* + * Trusted certificate store. + */ + if (pThis->hTrustedStore != NIL_RTCRSTORE) + rtCrX509CertPathsGetIssuersFromStore(pThis, pNode, pIssuer, pThis->hTrustedStore, + RTCRX509CERTPATHNODE_SRC_TRUSTED_STORE); + + /* + * Untrusted store. + */ + if (pThis->hUntrustedStore != NIL_RTCRSTORE) + rtCrX509CertPathsGetIssuersFromStore(pThis, pNode, pIssuer, pThis->hTrustedStore, + RTCRX509CERTPATHNODE_SRC_UNTRUSTED_STORE); + + /* + * Untrusted array. + */ + if (pThis->paUntrustedCerts) + for (uint32_t i = 0; i < pThis->cUntrustedCerts; i++) + if (RTCrX509Certificate_MatchSubjectOrAltSubjectByRfc5280(&pThis->paUntrustedCerts[i], pIssuer)) + rtCrX509CertPathsAddIssuer(pThis, pNode, &pThis->paUntrustedCerts[i], NULL, + RTCRX509CERTPATHNODE_SRC_UNTRUSTED_ARRAY); + + /** @todo Rainy day: Should abstract the untrusted array and set so we don't get + * unnecessary PKCS7/CMS header dependencies. */ + + /* + * Untrusted set. + */ + if (pThis->pUntrustedCertsSet) + { + uint32_t const cCerts = pThis->pUntrustedCertsSet->cItems; + PRTCRPKCS7CERT const *papCerts = pThis->pUntrustedCertsSet->papItems; + for (uint32_t i = 0; i < cCerts; i++) + { + PCRTCRPKCS7CERT pCert = papCerts[i]; + if ( pCert->enmChoice == RTCRPKCS7CERTCHOICE_X509 + && RTCrX509Certificate_MatchSubjectOrAltSubjectByRfc5280(pCert->u.pX509Cert, pIssuer)) + rtCrX509CertPathsAddIssuer(pThis, pNode, pCert->u.pX509Cert, NULL, RTCRX509CERTPATHNODE_SRC_UNTRUSTED_SET); + } + } +} + + +static PRTCRX509CERTPATHNODE rtCrX509CertPathsGetNextRightUp(PRTCRX509CERTPATHSINT pThis, PRTCRX509CERTPATHNODE pNode) +{ + for (;;) + { + /* The root node has no siblings. */ + PRTCRX509CERTPATHNODE pParent = pNode->pParent; + if (!pNode->pParent) + return NULL; + + /* Try go to the right. */ + PRTCRX509CERTPATHNODE pNext = RTListGetNext(&pParent->ChildListOrLeafEntry, pNode, RTCRX509CERTPATHNODE, SiblingEntry); + if (pNext) + return pNext; + + /* Up. */ + pNode = pParent; + } + + RT_NOREF_PV(pThis); +} + + +static PRTCRX509CERTPATHNODE rtCrX509CertPathsEliminatePath(PRTCRX509CERTPATHSINT pThis, PRTCRX509CERTPATHNODE pNode) +{ + for (;;) + { + Assert(RTListIsEmpty(&pNode->ChildListOrLeafEntry)); + + /* Don't remove the root node. */ + PRTCRX509CERTPATHNODE pParent = pNode->pParent; + if (!pParent) + return NULL; + + /* Before removing and deleting the node check if there is sibling + right to it that we should continue processing from. */ + PRTCRX509CERTPATHNODE pNext = RTListGetNext(&pParent->ChildListOrLeafEntry, pNode, RTCRX509CERTPATHNODE, SiblingEntry); + RTListNodeRemove(&pNode->SiblingEntry); + rtCrX509CertPathsDestroyNode(pNode); + + if (pNext) + return pNext; + + /* If the parent node cannot be removed, do a normal get-next-rigth-up + to find the continuation point for the tree loop. */ + if (!RTListIsEmpty(&pParent->ChildListOrLeafEntry)) + return rtCrX509CertPathsGetNextRightUp(pThis, pParent); + + pNode = pParent; + } +} + + +/** + * Destroys the whole path tree. + * + * @param pThis The path builder and verifier instance. + */ +static void rtCrX509CertPathsDestroyTree(PRTCRX509CERTPATHSINT pThis) +{ + PRTCRX509CERTPATHNODE pNode, pNextLeaf; + RTListForEachSafe(&pThis->LeafList, pNode, pNextLeaf, RTCRX509CERTPATHNODE, ChildListOrLeafEntry) + { + RTListNodeRemove(&pNode->ChildListOrLeafEntry); + RTListInit(&pNode->ChildListOrLeafEntry); + + for (;;) + { + PRTCRX509CERTPATHNODE pParent = pNode->pParent; + + RTListNodeRemove(&pNode->SiblingEntry); + rtCrX509CertPathsDestroyNode(pNode); + + if (!pParent) + { + pThis->pRoot = NULL; + break; + } + + if (!RTListIsEmpty(&pParent->ChildListOrLeafEntry)) + break; + + pNode = pParent; + } + } + Assert(!pThis->pRoot); +} + + +/** + * Adds a leaf node. + * + * This should normally be a trusted certificate, but the caller can also + * request the incomplete paths, in which case this will be an untrusted + * certificate. + * + * @returns Pointer to the next node in the tree to process. + * @param pThis The path builder instance. + * @param pNode The leaf node. + */ +static PRTCRX509CERTPATHNODE rtCrX509CertPathsAddLeaf(PRTCRX509CERTPATHSINT pThis, PRTCRX509CERTPATHNODE pNode) +{ + pNode->fLeaf = true; + + /* + * Priority insert by source and depth. + */ + PRTCRX509CERTPATHNODE pCurLeaf; + RTListForEach(&pThis->LeafList, pCurLeaf, RTCRX509CERTPATHNODE, ChildListOrLeafEntry) + { + if ( pNode->uSrc > pCurLeaf->uSrc + || ( pNode->uSrc == pCurLeaf->uSrc + && pNode->uDepth < pCurLeaf->uDepth) ) + { + RTListNodeInsertBefore(&pCurLeaf->ChildListOrLeafEntry, &pNode->ChildListOrLeafEntry); + pThis->cPaths++; + return rtCrX509CertPathsGetNextRightUp(pThis, pNode); + } + } + + RTListAppend(&pThis->LeafList, &pNode->ChildListOrLeafEntry); + pThis->cPaths++; + return rtCrX509CertPathsGetNextRightUp(pThis, pNode); +} + + + +RTDECL(int) RTCrX509CertPathsBuild(RTCRX509CERTPATHS hCertPaths, PRTERRINFO pErrInfo) +{ + /* + * Validate the input. + */ + PRTCRX509CERTPATHSINT pThis = hCertPaths; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRX509CERTPATHSINT_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(!(pThis->fFlags & ~RTCRX509CERTPATHSINT_F_VALID_MASK), VERR_INVALID_PARAMETER); + AssertReturn( (pThis->paUntrustedCerts == NULL && pThis->cUntrustedCerts == 0) + || (pThis->paUntrustedCerts != NULL && pThis->cUntrustedCerts > 0), + VERR_INVALID_PARAMETER); + AssertReturn(RTListIsEmpty(&pThis->LeafList), VERR_INVALID_PARAMETER); + AssertReturn(pThis->pRoot == NULL, VERR_INVALID_PARAMETER); + AssertReturn(pThis->rc == VINF_SUCCESS, pThis->rc); + AssertPtrReturn(pThis->pTarget, VERR_INVALID_PARAMETER); + Assert(RT_SUCCESS(RTCrX509Certificate_CheckSanity(pThis->pTarget, 0, NULL, NULL))); + + /* + * Set up the target. + */ + PRTCRX509CERTPATHNODE pCur; + pThis->pRoot = pCur = rtCrX509CertPathsNewNode(pThis); + if (pThis->pRoot) + { + pCur->pCert = pThis->pTarget; + pCur->uDepth = 0; + pCur->uSrc = RTCRX509CERTPATHNODE_SRC_TARGET; + + /* Check if the target is trusted and do the upgrade (this is outside the RFC, + but this simplifies the path validator usage a lot (less work for the caller)). */ + if ( pThis->pTrustedCert + && RTCrX509Certificate_Compare(pThis->pTrustedCert, pCur->pCert) == 0) + pCur->uSrc = RTCRX509CERTPATHNODE_SRC_TRUSTED_CERT; + else if ( pThis->hTrustedStore != NIL_RTCRSTORE + && rtCrX509CertPathsIsCertInStore(pCur, pThis->hTrustedStore)) + pCur->uSrc = RTCRX509CERTPATHNODE_SRC_TRUSTED_STORE; + + pThis->pErrInfo = pErrInfo; + + /* + * The tree construction loop. + * Walks down, up, and right as the tree is constructed. + */ + do + { + /* + * Check for the two leaf cases first. + */ + if (RTCRX509CERTPATHNODE_SRC_IS_TRUSTED(pCur->uSrc)) + pCur = rtCrX509CertPathsAddLeaf(pThis, pCur); +#if 0 /* This isn't right.*/ + else if (rtCrX509CertPathsIsSelfIssued(pCur)) + { + if (pThis->fFlags & RTCRX509CERTPATHSINT_F_ELIMINATE_UNTRUSTED_PATHS) + pCur = rtCrX509CertPathsEliminatePath(pThis, pCur); + else + pCur = rtCrX509CertPathsAddLeaf(pThis, pCur); + } +#endif + /* + * Not a leaf, find all potential issuers and decend into these. + */ + else + { + rtCrX509CertPathsGetIssuers(pThis, pCur); + if (RT_FAILURE(pThis->rc)) + break; + + if (!RTListIsEmpty(&pCur->ChildListOrLeafEntry)) + pCur = RTListGetFirst(&pCur->ChildListOrLeafEntry, RTCRX509CERTPATHNODE, SiblingEntry); + else if (pThis->fFlags & RTCRX509CERTPATHSINT_F_ELIMINATE_UNTRUSTED_PATHS) + pCur = rtCrX509CertPathsEliminatePath(pThis, pCur); + else + pCur = rtCrX509CertPathsAddLeaf(pThis, pCur); + } + if (pCur) + Log2(("RTCrX509CertPathsBuild: pCur=%p fLeaf=%d pParent=%p pNext=%p pPrev=%p\n", + pCur, pCur->fLeaf, pCur->pParent, + pCur->pParent ? RTListGetNext(&pCur->pParent->ChildListOrLeafEntry, pCur, RTCRX509CERTPATHNODE, SiblingEntry) : NULL, + pCur->pParent ? RTListGetPrev(&pCur->pParent->ChildListOrLeafEntry, pCur, RTCRX509CERTPATHNODE, SiblingEntry) : NULL)); + } while (pCur); + + pThis->pErrInfo = NULL; + if (RT_SUCCESS(pThis->rc)) + return VINF_SUCCESS; + } + else + Assert(RT_FAILURE_NP(pThis->rc)); + return pThis->rc; +} + + +/** + * Looks up path by leaf/path index. + * + * @returns Pointer to the leaf node of the path. + * @param pThis The path builder & validator instance. + * @param iPath The oridnal of the path to get. + */ +static PRTCRX509CERTPATHNODE rtCrX509CertPathsGetLeafByIndex(PRTCRX509CERTPATHSINT pThis, uint32_t iPath) +{ + Assert(iPath < pThis->cPaths); + + uint32_t iCurPath = 0; + PRTCRX509CERTPATHNODE pCurLeaf; + RTListForEach(&pThis->LeafList, pCurLeaf, RTCRX509CERTPATHNODE, ChildListOrLeafEntry) + { + if (iCurPath == iPath) + return pCurLeaf; + iCurPath++; + } + + AssertFailedReturn(NULL); +} + + +static void rtDumpPrintf(PFNRTDUMPPRINTFV pfnPrintfV, void *pvUser, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + pfnPrintfV(pvUser, pszFormat, va); + va_end(va); +} + + +static void rtDumpIndent(PFNRTDUMPPRINTFV pfnPrintfV, void *pvUser, uint32_t cchSpaces, const char *pszFormat, ...) +{ + static const char s_szSpaces[] = " "; + while (cchSpaces > 0) + { + uint32_t cchBurst = RT_MIN(sizeof(s_szSpaces) - 1, cchSpaces); + rtDumpPrintf(pfnPrintfV, pvUser, &s_szSpaces[sizeof(s_szSpaces) - cchBurst - 1]); + cchSpaces -= cchBurst; + } + + va_list va; + va_start(va, pszFormat); + pfnPrintfV(pvUser, pszFormat, va); + va_end(va); +} + +/** @name X.500 attribute types + * See RFC-4519 among others. + * @{ */ +#define RTCRX500_ID_AT_OBJECT_CLASS_OID "2.5.4.0" +#define RTCRX500_ID_AT_ALIASED_ENTRY_NAME_OID "2.5.4.1" +#define RTCRX500_ID_AT_KNOWLDGEINFORMATION_OID "2.5.4.2" +#define RTCRX500_ID_AT_COMMON_NAME_OID "2.5.4.3" +#define RTCRX500_ID_AT_SURNAME_OID "2.5.4.4" +#define RTCRX500_ID_AT_SERIAL_NUMBER_OID "2.5.4.5" +#define RTCRX500_ID_AT_COUNTRY_NAME_OID "2.5.4.6" +#define RTCRX500_ID_AT_LOCALITY_NAME_OID "2.5.4.7" +#define RTCRX500_ID_AT_STATE_OR_PROVINCE_NAME_OID "2.5.4.8" +#define RTCRX500_ID_AT_STREET_ADDRESS_OID "2.5.4.9" +#define RTCRX500_ID_AT_ORGANIZATION_NAME_OID "2.5.4.10" +#define RTCRX500_ID_AT_ORGANIZATION_UNIT_NAME_OID "2.5.4.11" +#define RTCRX500_ID_AT_TITLE_OID "2.5.4.12" +#define RTCRX500_ID_AT_DESCRIPTION_OID "2.5.4.13" +#define RTCRX500_ID_AT_SEARCH_GUIDE_OID "2.5.4.14" +#define RTCRX500_ID_AT_BUSINESS_CATEGORY_OID "2.5.4.15" +#define RTCRX500_ID_AT_POSTAL_ADDRESS_OID "2.5.4.16" +#define RTCRX500_ID_AT_POSTAL_CODE_OID "2.5.4.17" +#define RTCRX500_ID_AT_POST_OFFICE_BOX_OID "2.5.4.18" +#define RTCRX500_ID_AT_PHYSICAL_DELIVERY_OFFICE_NAME_OID "2.5.4.19" +#define RTCRX500_ID_AT_TELEPHONE_NUMBER_OID "2.5.4.20" +#define RTCRX500_ID_AT_TELEX_NUMBER_OID "2.5.4.21" +#define RTCRX500_ID_AT_TELETEX_TERMINAL_IDENTIFIER_OID "2.5.4.22" +#define RTCRX500_ID_AT_FACIMILE_TELEPHONE_NUMBER_OID "2.5.4.23" +#define RTCRX500_ID_AT_X121_ADDRESS_OID "2.5.4.24" +#define RTCRX500_ID_AT_INTERNATIONAL_ISDN_NUMBER_OID "2.5.4.25" +#define RTCRX500_ID_AT_REGISTERED_ADDRESS_OID "2.5.4.26" +#define RTCRX500_ID_AT_DESTINATION_INDICATOR_OID "2.5.4.27" +#define RTCRX500_ID_AT_PREFERRED_DELIVERY_METHOD_OID "2.5.4.28" +#define RTCRX500_ID_AT_PRESENTATION_ADDRESS_OID "2.5.4.29" +#define RTCRX500_ID_AT_SUPPORTED_APPLICATION_CONTEXT_OID "2.5.4.30" +#define RTCRX500_ID_AT_MEMBER_OID "2.5.4.31" +#define RTCRX500_ID_AT_OWNER_OID "2.5.4.32" +#define RTCRX500_ID_AT_ROLE_OCCUPANT_OID "2.5.4.33" +#define RTCRX500_ID_AT_SEE_ALSO_OID "2.5.4.34" +#define RTCRX500_ID_AT_USER_PASSWORD_OID "2.5.4.35" +#define RTCRX500_ID_AT_USER_CERTIFICATE_OID "2.5.4.36" +#define RTCRX500_ID_AT_CA_CERTIFICATE_OID "2.5.4.37" +#define RTCRX500_ID_AT_AUTHORITY_REVOCATION_LIST_OID "2.5.4.38" +#define RTCRX500_ID_AT_CERTIFICATE_REVOCATION_LIST_OID "2.5.4.39" +#define RTCRX500_ID_AT_CROSS_CERTIFICATE_PAIR_OID "2.5.4.40" +#define RTCRX500_ID_AT_NAME_OID "2.5.4.41" +#define RTCRX500_ID_AT_GIVEN_NAME_OID "2.5.4.42" +#define RTCRX500_ID_AT_INITIALS_OID "2.5.4.43" +#define RTCRX500_ID_AT_GENERATION_QUALIFIER_OID "2.5.4.44" +#define RTCRX500_ID_AT_UNIQUE_IDENTIFIER_OID "2.5.4.45" +#define RTCRX500_ID_AT_DN_QUALIFIER_OID "2.5.4.46" +#define RTCRX500_ID_AT_ENHANCHED_SEARCH_GUIDE_OID "2.5.4.47" +#define RTCRX500_ID_AT_PROTOCOL_INFORMATION_OID "2.5.4.48" +#define RTCRX500_ID_AT_DISTINGUISHED_NAME_OID "2.5.4.49" +#define RTCRX500_ID_AT_UNIQUE_MEMBER_OID "2.5.4.50" +#define RTCRX500_ID_AT_HOUSE_IDENTIFIER_OID "2.5.4.51" +#define RTCRX500_ID_AT_SUPPORTED_ALGORITHMS_OID "2.5.4.52" +#define RTCRX500_ID_AT_DELTA_REVOCATION_LIST_OID "2.5.4.53" +#define RTCRX500_ID_AT_ATTRIBUTE_CERTIFICATE_OID "2.5.4.58" +#define RTCRX500_ID_AT_PSEUDONYM_OID "2.5.4.65" +/** @} */ + + +static void rtCrX509NameDump(PCRTCRX509NAME pName, PFNRTDUMPPRINTFV pfnPrintfV, void *pvUser) +{ + for (uint32_t i = 0; i < pName->cItems; i++) + { + PCRTCRX509RELATIVEDISTINGUISHEDNAME const pRdn = pName->papItems[i]; + for (uint32_t j = 0; j < pRdn->cItems; j++) + { + PRTCRX509ATTRIBUTETYPEANDVALUE pAttrib = pRdn->papItems[j]; + + const char *pszType = RTCrX509Name_GetShortRdn(&pAttrib->Type); + if (!pszType) + pszType = pAttrib->Type.szObjId; + rtDumpPrintf(pfnPrintfV, pvUser, "/%s=", pszType); + if (pAttrib->Value.enmType == RTASN1TYPE_STRING) + { + if (pAttrib->Value.u.String.pszUtf8) + rtDumpPrintf(pfnPrintfV, pvUser, "%s", pAttrib->Value.u.String.pszUtf8); + else + { + const char *pch = pAttrib->Value.u.String.Asn1Core.uData.pch; + uint32_t cch = pAttrib->Value.u.String.Asn1Core.cb; + int rc = RTStrValidateEncodingEx(pch, cch, 0); + if (RT_SUCCESS(rc) && cch) + rtDumpPrintf(pfnPrintfV, pvUser, "%.*s", (size_t)cch, pch); + else + while (cch > 0) + { + if (RT_C_IS_PRINT(*pch)) + rtDumpPrintf(pfnPrintfV, pvUser, "%c", *pch); + else + rtDumpPrintf(pfnPrintfV, pvUser, "\\x%02x", *pch); + cch--; + pch++; + } + } + } + else + rtDumpPrintf(pfnPrintfV, pvUser, "<not-string: uTag=%#x>", pAttrib->Value.u.Core.uTag); + } + } +} + + +static const char *rtCrX509CertPathsNodeGetSourceName(PRTCRX509CERTPATHNODE pNode) +{ + switch (pNode->uSrc) + { + case RTCRX509CERTPATHNODE_SRC_TARGET: return "target"; + case RTCRX509CERTPATHNODE_SRC_UNTRUSTED_SET: return "untrusted_set"; + case RTCRX509CERTPATHNODE_SRC_UNTRUSTED_ARRAY: return "untrusted_array"; + case RTCRX509CERTPATHNODE_SRC_UNTRUSTED_STORE: return "untrusted_store"; + case RTCRX509CERTPATHNODE_SRC_TRUSTED_STORE: return "trusted_store"; + case RTCRX509CERTPATHNODE_SRC_TRUSTED_CERT: return "trusted_cert"; + default: return "invalid"; + } +} + + +static void rtCrX509CertPathsDumpOneWorker(PRTCRX509CERTPATHSINT pThis, uint32_t iPath, PRTCRX509CERTPATHNODE pCurLeaf, + uint32_t uVerbosity, PFNRTDUMPPRINTFV pfnPrintfV, void *pvUser) +{ + RT_NOREF_PV(pThis); + rtDumpPrintf(pfnPrintfV, pvUser, "Path #%u: %s, %u deep, rcVerify=%Rrc\n", + iPath, RTCRX509CERTPATHNODE_SRC_IS_TRUSTED(pCurLeaf->uSrc) ? "trusted" : "untrusted", pCurLeaf->uDepth, + pCurLeaf->rcVerify); + + for (uint32_t iIndent = 2; pCurLeaf; iIndent += 2, pCurLeaf = pCurLeaf->pParent) + { + if (pCurLeaf->pCert) + { + rtDumpIndent(pfnPrintfV, pvUser, iIndent, "Issuer : "); + rtCrX509NameDump(&pCurLeaf->pCert->TbsCertificate.Issuer, pfnPrintfV, pvUser); + rtDumpPrintf(pfnPrintfV, pvUser, "\n"); + + rtDumpIndent(pfnPrintfV, pvUser, iIndent, "Subject: "); + rtCrX509NameDump(&pCurLeaf->pCert->TbsCertificate.Subject, pfnPrintfV, pvUser); + rtDumpPrintf(pfnPrintfV, pvUser, "\n"); + + if (uVerbosity >= 4) + RTAsn1Dump(&pCurLeaf->pCert->SeqCore.Asn1Core, 0, iIndent, pfnPrintfV, pvUser); + else if (uVerbosity >= 3) + RTAsn1Dump(&pCurLeaf->pCert->TbsCertificate.T3.Extensions.SeqCore.Asn1Core, 0, iIndent, pfnPrintfV, pvUser); + + rtDumpIndent(pfnPrintfV, pvUser, iIndent, "Valid : %s thru %s\n", + RTTimeToString(&pCurLeaf->pCert->TbsCertificate.Validity.NotBefore.Time, + pThis->szTmp, sizeof(pThis->szTmp) / 2), + RTTimeToString(&pCurLeaf->pCert->TbsCertificate.Validity.NotAfter.Time, + &pThis->szTmp[sizeof(pThis->szTmp) / 2], sizeof(pThis->szTmp) / 2) ); + } + else + { + Assert(pCurLeaf->pCertCtx); Assert(pCurLeaf->pCertCtx->pTaInfo); + rtDumpIndent(pfnPrintfV, pvUser, iIndent, "Subject: "); + rtCrX509NameDump(&pCurLeaf->pCertCtx->pTaInfo->CertPath.TaName, pfnPrintfV, pvUser); + + if (uVerbosity >= 4) + RTAsn1Dump(&pCurLeaf->pCertCtx->pTaInfo->SeqCore.Asn1Core, 0, iIndent, pfnPrintfV, pvUser); + } + + const char *pszSrc = rtCrX509CertPathsNodeGetSourceName(pCurLeaf); + rtDumpIndent(pfnPrintfV, pvUser, iIndent, "Source : %s\n", pszSrc); + } +} + + +RTDECL(int) RTCrX509CertPathsDumpOne(RTCRX509CERTPATHS hCertPaths, uint32_t iPath, uint32_t uVerbosity, + PFNRTDUMPPRINTFV pfnPrintfV, void *pvUser) +{ + /* + * Validate the input. + */ + PRTCRX509CERTPATHSINT pThis = hCertPaths; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRX509CERTPATHSINT_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pfnPrintfV, VERR_INVALID_POINTER); + int rc; + if (iPath < pThis->cPaths) + { + PRTCRX509CERTPATHNODE pLeaf = rtCrX509CertPathsGetLeafByIndex(pThis, iPath); + if (pLeaf) + { + rtCrX509CertPathsDumpOneWorker(pThis, iPath, pLeaf, uVerbosity, pfnPrintfV, pvUser); + rc = VINF_SUCCESS; + } + else + rc = VERR_CR_X509_CERTPATHS_INTERNAL_ERROR; + } + else + rc = VERR_NOT_FOUND; + return rc; +} + + +RTDECL(int) RTCrX509CertPathsDumpAll(RTCRX509CERTPATHS hCertPaths, uint32_t uVerbosity, PFNRTDUMPPRINTFV pfnPrintfV, void *pvUser) +{ + /* + * Validate the input. + */ + PRTCRX509CERTPATHSINT pThis = hCertPaths; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRX509CERTPATHSINT_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pfnPrintfV, VERR_INVALID_POINTER); + + /* + * Dump all the paths. + */ + rtDumpPrintf(pfnPrintfV, pvUser, "%u paths, rc=%Rrc\n", pThis->cPaths, pThis->rc); + uint32_t iPath = 0; + PRTCRX509CERTPATHNODE pCurLeaf, pNextLeaf; + RTListForEachSafe(&pThis->LeafList, pCurLeaf, pNextLeaf, RTCRX509CERTPATHNODE, ChildListOrLeafEntry) + { + rtCrX509CertPathsDumpOneWorker(pThis, iPath, pCurLeaf, uVerbosity, pfnPrintfV, pvUser); + iPath++; + } + + return VINF_SUCCESS; +} + + +/** @} */ + + +/** @name Path Validator Functions. + * @{ + */ + + +static void *rtCrX509CpvAllocZ(PRTCRX509CERTPATHSINT pThis, size_t cb, const char *pszWhat) +{ + void *pv = RTMemAllocZ(cb); + if (!pv) + pThis->rc = RTErrInfoSetF(pThis->pErrInfo, VERR_NO_MEMORY, "Failed to allocate %zu bytes for %s", cb, pszWhat); + return pv; +} + + +DECL_NO_INLINE(static, bool) rtCrX509CpvFailed(PRTCRX509CERTPATHSINT pThis, int rc, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + pThis->rc = RTErrInfoSetV(pThis->pErrInfo, rc, pszFormat, va); + va_end(va); + return false; +} + + +/** + * Adds a sequence of excluded sub-trees. + * + * Don't waste time optimizing the output if this is supposed to be a union. + * Unless the path is very long, it's a lot more work to optimize and the result + * will be the same anyway. + * + * @returns success indicator. + * @param pThis The validator instance. + * @param pSubtrees The sequence of sub-trees to add. + */ +static bool rtCrX509CpvAddExcludedSubtrees(PRTCRX509CERTPATHSINT pThis, PCRTCRX509GENERALSUBTREES pSubtrees) +{ + if (((pThis->v.cExcludedSubtrees + 1) & 0xf) == 0) + { + void *pvNew = RTMemRealloc(pThis->v.papExcludedSubtrees, + (pThis->v.cExcludedSubtrees + 16) * sizeof(pThis->v.papExcludedSubtrees[0])); + if (RT_UNLIKELY(!pvNew)) + return rtCrX509CpvFailed(pThis, VERR_NO_MEMORY, "Error growing subtrees pointer array to %u elements", + pThis->v.cExcludedSubtrees + 16); + pThis->v.papExcludedSubtrees = (PCRTCRX509GENERALSUBTREES *)pvNew; + } + pThis->v.papExcludedSubtrees[pThis->v.cExcludedSubtrees] = pSubtrees; + pThis->v.cExcludedSubtrees++; + return true; +} + + +/** + * Checks if a sub-tree is according to RFC-5280. + * + * @returns Success indiciator. + * @param pThis The validator instance. + * @param pSubtree The subtree to check. + */ +static bool rtCrX509CpvCheckSubtreeValidity(PRTCRX509CERTPATHSINT pThis, PCRTCRX509GENERALSUBTREE pSubtree) +{ + if ( pSubtree->Base.enmChoice <= RTCRX509GENERALNAMECHOICE_INVALID + || pSubtree->Base.enmChoice >= RTCRX509GENERALNAMECHOICE_END) + return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_UNEXP_GENERAL_SUBTREE_CHOICE, + "Unexpected GeneralSubtree choice %#x", pSubtree->Base.enmChoice); + + if (RTAsn1Integer_UnsignedCompareWithU32(&pSubtree->Minimum, 0) != 0) + return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_UNEXP_GENERAL_SUBTREE_MIN, + "Unexpected GeneralSubtree Minimum value: %#llx", + pSubtree->Minimum.uValue); + + if (RTAsn1Integer_IsPresent(&pSubtree->Maximum)) + return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_UNEXP_GENERAL_SUBTREE_MAX, + "Unexpected GeneralSubtree Maximum value: %#llx", + pSubtree->Maximum.uValue); + + return true; +} + + +/** + * Grows the array of permitted sub-trees. + * + * @returns success indiciator. + * @param pThis The validator instance. + * @param cAdding The number of subtrees we should grow by + * (relative to the current number of valid + * entries). + */ +static bool rtCrX509CpvGrowPermittedSubtrees(PRTCRX509CERTPATHSINT pThis, uint32_t cAdding) +{ + uint32_t cNew = RT_ALIGN_32(pThis->v.cPermittedSubtrees + cAdding, 16); + if (cNew > pThis->v.cPermittedSubtreesAlloc) + { + if (cNew >= _4K) + return rtCrX509CpvFailed(pThis, VERR_NO_MEMORY, "Too many permitted subtrees: %u (cur %u)", + cNew, pThis->v.cPermittedSubtrees); + void *pvNew = RTMemRealloc(pThis->v.papPermittedSubtrees, cNew * sizeof(pThis->v.papPermittedSubtrees[0])); + if (RT_UNLIKELY(!pvNew)) + return rtCrX509CpvFailed(pThis, VERR_NO_MEMORY, "Error growing subtrees pointer array from %u to %u elements", + pThis->v.cPermittedSubtreesAlloc, cNew); + pThis->v.papPermittedSubtrees = (PCRTCRX509GENERALSUBTREE *)pvNew; + } + return true; +} + + +/** + * Adds a sequence of permitted sub-trees. + * + * We store reference to each individual sub-tree because we must support + * intersection calculation. + * + * @returns success indiciator. + * @param pThis The validator instance. + * @param cSubtrees The number of sub-trees to add. + * @param papSubtrees Array of sub-trees to add. + */ +static bool rtCrX509CpvAddPermittedSubtrees(PRTCRX509CERTPATHSINT pThis, uint32_t cSubtrees, + PRTCRX509GENERALSUBTREE const *papSubtrees) +{ + /* + * If the array is empty, assume no permitted names. + */ + if (!cSubtrees) + { + pThis->v.fNoPermittedSubtrees = true; + return true; + } + + /* + * Grow the array if necessary. + */ + if (!rtCrX509CpvGrowPermittedSubtrees(pThis, cSubtrees)) + return false; + + /* + * Append each subtree to the array. + */ + uint32_t iDst = pThis->v.cPermittedSubtrees; + for (uint32_t iSrc = 0; iSrc < cSubtrees; iSrc++) + { + if (!rtCrX509CpvCheckSubtreeValidity(pThis, papSubtrees[iSrc])) + return false; + pThis->v.papPermittedSubtrees[iDst] = papSubtrees[iSrc]; + iDst++; + } + pThis->v.cPermittedSubtrees = iDst; + + return true; +} + + +/** + * Adds a one permitted sub-tree. + * + * We store reference to each individual sub-tree because we must support + * intersection calculation. + * + * @returns success indiciator. + * @param pThis The validator instance. + * @param pSubtree Array of sub-trees to add. + */ +static bool rtCrX509CpvAddPermittedSubtree(PRTCRX509CERTPATHSINT pThis, PCRTCRX509GENERALSUBTREE pSubtree) +{ + return rtCrX509CpvAddPermittedSubtrees(pThis, 1, (PRTCRX509GENERALSUBTREE const *)&pSubtree); +} + + +/** + * Calculates the intersection between @a pSubtrees and the current permitted + * sub-trees. + * + * @returns Success indicator. + * @param pThis The validator instance. + * @param pSubtrees The sub-tree sequence to intersect with. + */ +static bool rtCrX509CpvIntersectionPermittedSubtrees(PRTCRX509CERTPATHSINT pThis, PCRTCRX509GENERALSUBTREES pSubtrees) +{ + /* + * Deal with special cases first. + */ + if (pThis->v.fNoPermittedSubtrees) + { + Assert(pThis->v.cPermittedSubtrees == 0); + return true; + } + + uint32_t cRight = pSubtrees->cItems; + PRTCRX509GENERALSUBTREE const *papRight = pSubtrees->papItems; + if (cRight == 0) + { + pThis->v.cPermittedSubtrees = 0; + pThis->v.fNoPermittedSubtrees = true; + return true; + } + + uint32_t cLeft = pThis->v.cPermittedSubtrees; + PCRTCRX509GENERALSUBTREE *papLeft = pThis->v.papPermittedSubtrees; + if (!cLeft) /* first name constraint, no initial constraint */ + return rtCrX509CpvAddPermittedSubtrees(pThis, cRight, papRight); + + /* + * Create a new array with the intersection, freeing the old (left) array + * once we're done. + */ + bool afRightTags[RTCRX509GENERALNAMECHOICE_END] = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + + pThis->v.cPermittedSubtrees = 0; + pThis->v.cPermittedSubtreesAlloc = 0; + pThis->v.papPermittedSubtrees = NULL; + + for (uint32_t iRight = 0; iRight < cRight; iRight++) + { + if (!rtCrX509CpvCheckSubtreeValidity(pThis, papRight[iRight])) + return false; + + RTCRX509GENERALNAMECHOICE const enmRightChoice = papRight[iRight]->Base.enmChoice; + afRightTags[enmRightChoice] = true; + + bool fHaveRight = false; + for (uint32_t iLeft = 0; iLeft < cLeft; iLeft++) + if (papLeft[iLeft]->Base.enmChoice == enmRightChoice) + { + if (RTCrX509GeneralSubtree_Compare(papLeft[iLeft], papRight[iRight]) == 0) + { + if (!fHaveRight) + { + fHaveRight = true; + rtCrX509CpvAddPermittedSubtree(pThis, papLeft[iLeft]); + } + } + else if (RTCrX509GeneralSubtree_ConstraintMatch(papLeft[iLeft], papRight[iRight])) + { + if (!fHaveRight) + { + fHaveRight = true; + rtCrX509CpvAddPermittedSubtree(pThis, papRight[iRight]); + } + } + else if (RTCrX509GeneralSubtree_ConstraintMatch(papRight[iRight], papLeft[iLeft])) + rtCrX509CpvAddPermittedSubtree(pThis, papLeft[iLeft]); + } + } + + /* + * Add missing types not specified in the right set. + */ + for (uint32_t iLeft = 0; iLeft < cLeft; iLeft++) + if (!afRightTags[papLeft[iLeft]->Base.enmChoice]) + rtCrX509CpvAddPermittedSubtree(pThis, papLeft[iLeft]); + + /* + * If we ended up with an empty set, no names are permitted any more. + */ + if (pThis->v.cPermittedSubtrees == 0) + pThis->v.fNoPermittedSubtrees = true; + + RTMemFree(papLeft); + return RT_SUCCESS(pThis->rc); +} + + +/** + * Check if the given X.509 name is permitted by current name constraints. + * + * @returns true is permitteded, false if not (caller set error info). + * @param pThis The validator instance. + * @param pName The name to match. + */ +static bool rtCrX509CpvIsNamePermitted(PRTCRX509CERTPATHSINT pThis, PCRTCRX509NAME pName) +{ + uint32_t i = pThis->v.cPermittedSubtrees; + if (i == 0) + return !pThis->v.fNoPermittedSubtrees; + + while (i-- > 0) + { + PCRTCRX509GENERALSUBTREE pConstraint = pThis->v.papPermittedSubtrees[i]; + if ( RTCRX509GENERALNAME_IS_DIRECTORY_NAME(&pConstraint->Base) + && RTCrX509Name_ConstraintMatch(&pConstraint->Base.u.pT4->DirectoryName, pName)) + return true; + } + return false; +} + + +/** + * Check if the given X.509 general name is permitted by current name + * constraints. + * + * @returns true is permitteded, false if not (caller sets error info). + * @param pThis The validator instance. + * @param pGeneralName The name to match. + */ +static bool rtCrX509CpvIsGeneralNamePermitted(PRTCRX509CERTPATHSINT pThis, PCRTCRX509GENERALNAME pGeneralName) +{ + uint32_t i = pThis->v.cPermittedSubtrees; + if (i == 0) + return !pThis->v.fNoPermittedSubtrees; + + while (i-- > 0) + if (RTCrX509GeneralName_ConstraintMatch(&pThis->v.papPermittedSubtrees[i]->Base, pGeneralName)) + return true; + return false; +} + + +/** + * Check if the given X.509 name is excluded by current name constraints. + * + * @returns true if excluded (caller sets error info), false if not explicitly + * excluded. + * @param pThis The validator instance. + * @param pName The name to match. + */ +static bool rtCrX509CpvIsNameExcluded(PRTCRX509CERTPATHSINT pThis, PCRTCRX509NAME pName) +{ + uint32_t i = pThis->v.cExcludedSubtrees; + while (i-- > 0) + { + PCRTCRX509GENERALSUBTREES pSubTrees = pThis->v.papExcludedSubtrees[i]; + uint32_t j = pSubTrees->cItems; + while (j-- > 0) + { + PCRTCRX509GENERALSUBTREE const pSubTree = pSubTrees->papItems[j]; + if ( RTCRX509GENERALNAME_IS_DIRECTORY_NAME(&pSubTree->Base) + && RTCrX509Name_ConstraintMatch(&pSubTree->Base.u.pT4->DirectoryName, pName)) + return true; + } + } + return false; +} + + +/** + * Check if the given X.509 general name is excluded by current name + * constraints. + * + * @returns true if excluded (caller sets error info), false if not explicitly + * excluded. + * @param pThis The validator instance. + * @param pGeneralName The name to match. + */ +static bool rtCrX509CpvIsGeneralNameExcluded(PRTCRX509CERTPATHSINT pThis, PCRTCRX509GENERALNAME pGeneralName) +{ + uint32_t i = pThis->v.cExcludedSubtrees; + while (i-- > 0) + { + PCRTCRX509GENERALSUBTREES pSubTrees = pThis->v.papExcludedSubtrees[i]; + uint32_t j = pSubTrees->cItems; + while (j-- > 0) + if (RTCrX509GeneralName_ConstraintMatch(&pSubTrees->papItems[j]->Base, pGeneralName)) + return true; + } + return false; +} + + +/** + * Creates a new node and inserts it. + * + * @param pThis The path builder & validator instance. + * @param pParent The parent node. NULL for the root node. + * @param iDepth The tree depth to insert at. + * @param pValidPolicy The valid policy of the new node. + * @param pQualifiers The qualifiers of the new node. + * @param pExpectedPolicy The (first) expected polcy of the new node. + */ +static bool rtCrX509CpvPolicyTreeInsertNew(PRTCRX509CERTPATHSINT pThis, PRTCRX509CERTPATHSPOLICYNODE pParent, uint32_t iDepth, + PCRTASN1OBJID pValidPolicy, PCRTCRX509POLICYQUALIFIERINFOS pQualifiers, + PCRTASN1OBJID pExpectedPolicy) +{ + Assert(iDepth <= pThis->v.cNodes); + + PRTCRX509CERTPATHSPOLICYNODE pNode; + pNode = (PRTCRX509CERTPATHSPOLICYNODE)rtCrX509CpvAllocZ(pThis, sizeof(*pNode), "policy tree node"); + if (pNode) + { + pNode->pParent = pParent; + if (pParent) + RTListAppend(&pParent->ChildList, &pNode->SiblingEntry); + else + { + Assert(pThis->v.pValidPolicyTree == NULL); + pThis->v.pValidPolicyTree = pNode; + RTListInit(&pNode->SiblingEntry); + } + RTListInit(&pNode->ChildList); + RTListAppend(&pThis->v.paValidPolicyDepthLists[iDepth], &pNode->DepthEntry); + + pNode->pValidPolicy = pValidPolicy; + pNode->pPolicyQualifiers = pQualifiers; + pNode->pExpectedPolicyFirst = pExpectedPolicy; + pNode->cMoreExpectedPolicySet = 0; + pNode->papMoreExpectedPolicySet = NULL; + return true; + } + return false; +} + + +/** + * Unlinks and frees a node in the valid policy tree. + * + * @param pThis The path builder & validator instance. + * @param pNode The node to destroy. + */ +static void rtCrX509CpvPolicyTreeDestroyNode(PRTCRX509CERTPATHSINT pThis, PRTCRX509CERTPATHSPOLICYNODE pNode) +{ + Assert(RTListIsEmpty(&pNode->ChildList)); + if (pNode->pParent) + RTListNodeRemove(&pNode->SiblingEntry); + else + pThis->v.pValidPolicyTree = NULL; + RTListNodeRemove(&pNode->DepthEntry); + pNode->pParent = NULL; + + if (pNode->papMoreExpectedPolicySet) + { + RTMemFree(pNode->papMoreExpectedPolicySet); + pNode->papMoreExpectedPolicySet = NULL; + } + RTMemFree(pNode); +} + + +/** + * Unlinks and frees a sub-tree in the valid policy tree. + * + * @param pThis The path builder & validator instance. + * @param pNode The node that is the root of the subtree. + */ +static void rtCrX509CpvPolicyTreeDestroySubtree(PRTCRX509CERTPATHSINT pThis, PRTCRX509CERTPATHSPOLICYNODE pNode) +{ + if (!RTListIsEmpty(&pNode->ChildList)) + { + PRTCRX509CERTPATHSPOLICYNODE pCur = pNode; + do + { + Assert(!RTListIsEmpty(&pCur->ChildList)); + + /* Decend until we find a leaf. */ + do + pCur = RTListGetFirst(&pCur->ChildList, RTCRX509CERTPATHSPOLICYNODE, SiblingEntry); + while (!RTListIsEmpty(&pCur->ChildList)); + + /* Remove it and all leafy siblings. */ + PRTCRX509CERTPATHSPOLICYNODE pParent = pCur->pParent; + do + { + Assert(pCur != pNode); + rtCrX509CpvPolicyTreeDestroyNode(pThis, pCur); + pCur = RTListGetFirst(&pParent->ChildList, RTCRX509CERTPATHSPOLICYNODE, SiblingEntry); + if (!pCur) + { + pCur = pParent; + pParent = pParent->pParent; + } + } while (RTListIsEmpty(&pCur->ChildList) && pCur != pNode); + } while (pCur != pNode); + } + + rtCrX509CpvPolicyTreeDestroyNode(pThis, pNode); +} + + + +/** + * Destroys the entire policy tree. + * + * @param pThis The path builder & validator instance. + */ +static void rtCrX509CpvPolicyTreeDestroy(PRTCRX509CERTPATHSINT pThis) +{ + uint32_t i = pThis->v.cNodes + 1; + while (i-- > 0) + { + PRTCRX509CERTPATHSPOLICYNODE pCur, pNext; + RTListForEachSafe(&pThis->v.paValidPolicyDepthLists[i], pCur, pNext, RTCRX509CERTPATHSPOLICYNODE, DepthEntry) + { + rtCrX509CpvPolicyTreeDestroyNode(pThis, pCur); + } + } +} + + +/** + * Removes all leaf nodes at level @a iDepth and above. + * + * @param pThis The path builder & validator instance. + * @param iDepth The depth to start pruning at. + */ +static void rtCrX509CpvPolicyTreePrune(PRTCRX509CERTPATHSINT pThis, uint32_t iDepth) +{ + do + { + PRTLISTANCHOR pList = &pThis->v.paValidPolicyDepthLists[iDepth]; + PRTCRX509CERTPATHSPOLICYNODE pCur, pNext; + RTListForEachSafe(pList, pCur, pNext, RTCRX509CERTPATHSPOLICYNODE, DepthEntry) + { + if (RTListIsEmpty(&pCur->ChildList)) + rtCrX509CpvPolicyTreeDestroyNode(pThis, pCur); + } + + } while (iDepth-- > 0); +} + + +/** + * Checks if @a pPolicy is the valid policy of a child of @a pNode. + * + * @returns true if in child node, false if not. + * @param pNode The node which children to check. + * @param pPolicy The valid policy to look for among the children. + */ +static bool rtCrX509CpvPolicyTreeIsChild(PRTCRX509CERTPATHSPOLICYNODE pNode, PCRTASN1OBJID pPolicy) +{ + PRTCRX509CERTPATHSPOLICYNODE pChild; + RTListForEach(&pNode->ChildList, pChild, RTCRX509CERTPATHSPOLICYNODE, SiblingEntry) + { + if (RTAsn1ObjId_Compare(pChild->pValidPolicy, pPolicy) == 0) + return true; + } + return true; +} + + +/** + * Prunes the valid policy tree according to the specified user policy set. + * + * @returns Pointer to the policy object from @a papPolicies if found, NULL if + * no match. + * @param pObjId The object ID to locate at match in the set. + * @param cPolicies The number of policies in @a papPolicies. + * @param papPolicies The policy set to search. + */ +static PCRTASN1OBJID rtCrX509CpvFindObjIdInPolicySet(PCRTASN1OBJID pObjId, uint32_t cPolicies, PCRTASN1OBJID *papPolicies) +{ + uint32_t i = cPolicies; + while (i-- > 0) + if (RTAsn1ObjId_Compare(pObjId, papPolicies[i]) == 0) + return papPolicies[i]; + return NULL; +} + + +/** + * Prunes the valid policy tree according to the specified user policy set. + * + * @returns success indicator (allocates memory) + * @param pThis The path builder & validator instance. + * @param cPolicies The number of policies in @a papPolicies. + * @param papPolicies The user initial policies. + */ +static bool rtCrX509CpvPolicyTreeIntersect(PRTCRX509CERTPATHSINT pThis, uint32_t cPolicies, PCRTASN1OBJID *papPolicies) +{ + /* + * 4.1.6.g.i - NULL tree remains NULL. + */ + if (!pThis->v.pValidPolicyTree) + return true; + + /* + * 4.1.6.g.ii - If the user set includes anyPolicy, the whole tree is the + * result of the intersection. + */ + uint32_t i = cPolicies; + while (i-- > 0) + if (RTAsn1ObjId_CompareWithString(papPolicies[i], RTCRX509_ID_CE_CP_ANY_POLICY_OID) == 0) + return true; + + /* + * 4.1.6.g.iii - Complicated. + */ + PRTCRX509CERTPATHSPOLICYNODE pCur, pNext; + PRTLISTANCHOR pList; + + /* 1 & 2: Delete nodes which parent has valid policy == anyPolicy and which + valid policy is neither anyPolicy nor a member of papszPolicies. + While doing so, construct a set of unused user policies that + we'll replace anyPolicy nodes with in step 3. */ + uint32_t cPoliciesLeft = 0; + PCRTASN1OBJID *papPoliciesLeft = NULL; + if (cPolicies) + { + papPoliciesLeft = (PCRTASN1OBJID *)rtCrX509CpvAllocZ(pThis, cPolicies * sizeof(papPoliciesLeft[0]), "papPoliciesLeft"); + if (!papPoliciesLeft) + return false; + for (i = 0; i < cPolicies; i++) + papPoliciesLeft[i] = papPolicies[i]; + } + + for (uint32_t iDepth = 1; iDepth <= pThis->v.cNodes; iDepth++) + { + pList = &pThis->v.paValidPolicyDepthLists[iDepth]; + RTListForEachSafe(pList, pCur, pNext, RTCRX509CERTPATHSPOLICYNODE, DepthEntry) + { + Assert(pCur->pParent); + if ( RTAsn1ObjId_CompareWithString(pCur->pParent->pValidPolicy, RTCRX509_ID_CE_CP_ANY_POLICY_OID) == 0 + && RTAsn1ObjId_CompareWithString(pCur->pValidPolicy, RTCRX509_ID_CE_CP_ANY_POLICY_OID) != 0) + { + PCRTASN1OBJID pFound = rtCrX509CpvFindObjIdInPolicySet(pCur->pValidPolicy, cPolicies, papPolicies); + if (!pFound) + rtCrX509CpvPolicyTreeDestroySubtree(pThis, pCur); + else + for (i = 0; i < cPoliciesLeft; i++) + if (papPoliciesLeft[i] == pFound) + { + cPoliciesLeft--; + if (i < cPoliciesLeft) + papPoliciesLeft[i] = papPoliciesLeft[cPoliciesLeft]; + papPoliciesLeft[cPoliciesLeft] = NULL; + break; + } + } + } + } + + /* + * 4.1.5.g.iii.3 - Replace anyPolicy nodes on the final tree depth with + * the policies in papPoliciesLeft. + */ + pList = &pThis->v.paValidPolicyDepthLists[pThis->v.cNodes]; + RTListForEachSafe(pList, pCur, pNext, RTCRX509CERTPATHSPOLICYNODE, DepthEntry) + { + if (RTAsn1ObjId_CompareWithString(pCur->pValidPolicy, RTCRX509_ID_CE_CP_ANY_POLICY_OID) == 0) + { + for (i = 0; i < cPoliciesLeft; i++) + rtCrX509CpvPolicyTreeInsertNew(pThis, pCur->pParent, pThis->v.cNodes - 1, + papPoliciesLeft[i], pCur->pPolicyQualifiers, papPoliciesLeft[i]); + rtCrX509CpvPolicyTreeDestroyNode(pThis, pCur); + } + } + + RTMemFree(papPoliciesLeft); + + /* + * 4.1.5.g.iii.4 - Prune the tree + */ + rtCrX509CpvPolicyTreePrune(pThis, pThis->v.cNodes - 1); + + return RT_SUCCESS(pThis->rc); +} + + + +/** + * Frees the path validator state. + * + * @param pThis The path builder & validator instance. + */ +static void rtCrX509CpvCleanup(PRTCRX509CERTPATHSINT pThis) +{ + /* + * Destroy the policy tree and all its nodes. We do this from the bottom + * up via the depth lists, saving annoying tree traversal. + */ + if (pThis->v.paValidPolicyDepthLists) + { + rtCrX509CpvPolicyTreeDestroy(pThis); + + RTMemFree(pThis->v.paValidPolicyDepthLists); + pThis->v.paValidPolicyDepthLists = NULL; + } + + Assert(pThis->v.pValidPolicyTree == NULL); + pThis->v.pValidPolicyTree = NULL; + + /* + * Destroy the name constraint arrays. + */ + if (pThis->v.papPermittedSubtrees) + { + RTMemFree(pThis->v.papPermittedSubtrees); + pThis->v.papPermittedSubtrees = NULL; + } + pThis->v.cPermittedSubtrees = 0; + pThis->v.cPermittedSubtreesAlloc = 0; + pThis->v.fNoPermittedSubtrees = false; + + if (pThis->v.papExcludedSubtrees) + { + RTMemFree(pThis->v.papExcludedSubtrees); + pThis->v.papExcludedSubtrees = NULL; + } + pThis->v.cExcludedSubtrees = 0; + + /* + * Clear other pointers. + */ + pThis->v.pWorkingIssuer = NULL; + pThis->v.pWorkingPublicKey = NULL; + pThis->v.pWorkingPublicKeyAlgorithm = NULL; + pThis->v.pWorkingPublicKeyParameters = NULL; +} + + +/** + * Initializes the state. + * + * Caller must check pThis->rc. + * + * @param pThis The path builder & validator instance. + * @param pTrustAnchor The trust anchor node for the path that we're about + * to validate. + */ +static void rtCrX509CpvInit(PRTCRX509CERTPATHSINT pThis, PRTCRX509CERTPATHNODE pTrustAnchor) +{ + rtCrX509CpvCleanup(pThis); + + /* + * The node count does not include the trust anchor. + */ + pThis->v.cNodes = pTrustAnchor->uDepth; + + /* + * Valid policy tree starts with an anyPolicy node. + */ + uint32_t i = pThis->v.cNodes + 1; + pThis->v.paValidPolicyDepthLists = (PRTLISTANCHOR)rtCrX509CpvAllocZ(pThis, i * sizeof(RTLISTANCHOR), + "paValidPolicyDepthLists"); + if (RT_UNLIKELY(!pThis->v.paValidPolicyDepthLists)) + return; + while (i-- > 0) + RTListInit(&pThis->v.paValidPolicyDepthLists[i]); + + if (!rtCrX509CpvPolicyTreeInsertNew(pThis, NULL, 0 /* iDepth*/, &pThis->AnyPolicyObjId, NULL, &pThis->AnyPolicyObjId)) + return; + Assert(!RTListIsEmpty(&pThis->v.paValidPolicyDepthLists[0])); Assert(pThis->v.pValidPolicyTree); + + /* + * Name constrains. + */ + if (pThis->pInitialPermittedSubtrees) + rtCrX509CpvAddPermittedSubtrees(pThis, pThis->pInitialPermittedSubtrees->cItems, + pThis->pInitialPermittedSubtrees->papItems); + if (pThis->pInitialExcludedSubtrees) + rtCrX509CpvAddExcludedSubtrees(pThis, pThis->pInitialExcludedSubtrees); + + /* + * Counters. + */ + pThis->v.cExplicitPolicy = pThis->cInitialExplicitPolicy; + pThis->v.cInhibitPolicyMapping = pThis->cInitialPolicyMappingInhibit; + pThis->v.cInhibitAnyPolicy = pThis->cInitialInhibitAnyPolicy; + pThis->v.cMaxPathLength = pThis->v.cNodes; + + /* + * Certificate info from the trust anchor. + */ + if (pTrustAnchor->pCert) + { + PCRTCRX509TBSCERTIFICATE const pTbsCert = &pTrustAnchor->pCert->TbsCertificate; + pThis->v.pWorkingIssuer = &pTbsCert->Subject; + pThis->v.pWorkingPublicKey = &pTbsCert->SubjectPublicKeyInfo.SubjectPublicKey; + pThis->v.pWorkingPublicKeyAlgorithm = &pTbsCert->SubjectPublicKeyInfo.Algorithm.Algorithm; + pThis->v.pWorkingPublicKeyParameters = &pTbsCert->SubjectPublicKeyInfo.Algorithm.Parameters; + } + else + { + Assert(pTrustAnchor->pCertCtx); Assert(pTrustAnchor->pCertCtx->pTaInfo); + + PCRTCRTAFTRUSTANCHORINFO const pTaInfo = pTrustAnchor->pCertCtx->pTaInfo; + pThis->v.pWorkingIssuer = &pTaInfo->CertPath.TaName; + pThis->v.pWorkingPublicKey = &pTaInfo->PubKey.SubjectPublicKey; + pThis->v.pWorkingPublicKeyAlgorithm = &pTaInfo->PubKey.Algorithm.Algorithm; + pThis->v.pWorkingPublicKeyParameters = &pTaInfo->PubKey.Algorithm.Parameters; + } + if ( !RTASN1CORE_IS_PRESENT(&pThis->v.pWorkingPublicKeyParameters->u.Core) + || pThis->v.pWorkingPublicKeyParameters->enmType == RTASN1TYPE_NULL) + pThis->v.pWorkingPublicKeyParameters = NULL; +} + + +/** + * This does basic trust anchor checks (similar to 6.1.3.a) before starting on + * the RFC-5280 algorithm. + */ +static bool rtCrX509CpvMaybeCheckTrustAnchor(PRTCRX509CERTPATHSINT pThis, PRTCRX509CERTPATHNODE pTrustAnchor) +{ + /* + * This is optional (not part of RFC-5280) and we need a full certificate + * structure to do it. + */ + if (!(pThis->fFlags & RTCRX509CERTPATHSINT_F_CHECK_TRUST_ANCHOR)) + return true; + + PCRTCRX509CERTIFICATE const pCert = pTrustAnchor->pCert; + if (!pCert) + return true; + + /* + * Verify the certificate signature if self-signed. + */ + if (RTCrX509Certificate_IsSelfSigned(pCert)) + { + int rc = RTCrX509Certificate_VerifySignature(pCert, pThis->v.pWorkingPublicKeyAlgorithm, + pThis->v.pWorkingPublicKeyParameters, pThis->v.pWorkingPublicKey, + pThis->pErrInfo); + if (RT_FAILURE(rc)) + { + pThis->rc = rc; + return false; + } + } + + /* + * Verify that the certificate is valid at the specified time. + */ + AssertCompile(sizeof(pThis->szTmp) >= 36 * 3); + if ( (pThis->fFlags & RTCRX509CERTPATHSINT_F_VALID_TIME) + && !RTCrX509Validity_IsValidAtTimeSpec(&pCert->TbsCertificate.Validity, &pThis->ValidTime)) + return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_NOT_VALID_AT_TIME, + "Certificate is not valid (ValidTime=%s Validity=[%s...%s])", + RTTimeSpecToString(&pThis->ValidTime, &pThis->szTmp[0], 36), + RTTimeToString(&pCert->TbsCertificate.Validity.NotBefore.Time, &pThis->szTmp[36], 36), + RTTimeToString(&pCert->TbsCertificate.Validity.NotAfter.Time, &pThis->szTmp[2*36], 36) ); + + /* + * Verified that the certficiate is not revoked. + */ + /** @todo rainy day. */ + + /* + * If non-leaf certificate CA must be set, if basic constraints are present. + */ + if (pTrustAnchor->pParent) + { + if (RTAsn1Integer_UnsignedCompareWithU32(&pTrustAnchor->pCert->TbsCertificate.T0.Version, RTCRX509TBSCERTIFICATE_V3) != 0) + return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_NOT_V3_CERT, + "Only version 3 TA certificates are supported (Version=%llu)", + pTrustAnchor->pCert->TbsCertificate.T0.Version.uValue); + PCRTCRX509BASICCONSTRAINTS pBasicConstraints = pTrustAnchor->pCert->TbsCertificate.T3.pBasicConstraints; + if (pBasicConstraints && !pBasicConstraints->CA.fValue) + return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_NOT_CA_CERT, + "Trust anchor certificate is not marked as a CA"); + } + + return true; +} + + +/** + * Step 6.1.3.a. + */ +static bool rtCrX509CpvCheckBasicCertInfo(PRTCRX509CERTPATHSINT pThis, PRTCRX509CERTPATHNODE pNode) +{ + /* + * 6.1.3.a.1 - Verify the certificate signature. + */ + int rc = RTCrX509Certificate_VerifySignature(pNode->pCert, pThis->v.pWorkingPublicKeyAlgorithm, + pThis->v.pWorkingPublicKeyParameters, pThis->v.pWorkingPublicKey, + pThis->pErrInfo); + if (RT_FAILURE(rc)) + { + pThis->rc = rc; + return false; + } + + /* + * 6.1.3.a.2 - Verify that the certificate is valid at the specified time. + */ + AssertCompile(sizeof(pThis->szTmp) >= 36 * 3); + if ( (pThis->fFlags & RTCRX509CERTPATHSINT_F_VALID_TIME) + && !RTCrX509Validity_IsValidAtTimeSpec(&pNode->pCert->TbsCertificate.Validity, &pThis->ValidTime)) + return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_NOT_VALID_AT_TIME, + "Certificate is not valid (ValidTime=%s Validity=[%s...%s])", + RTTimeSpecToString(&pThis->ValidTime, &pThis->szTmp[0], 36), + RTTimeToString(&pNode->pCert->TbsCertificate.Validity.NotBefore.Time, &pThis->szTmp[36], 36), + RTTimeToString(&pNode->pCert->TbsCertificate.Validity.NotAfter.Time, &pThis->szTmp[2*36], 36) ); + + /* + * 6.1.3.a.3 - Verified that the certficiate is not revoked. + */ + /** @todo rainy day. */ + + /* + * 6.1.3.a.4 - Check the issuer name. + */ + if (!RTCrX509Name_MatchByRfc5280(&pNode->pCert->TbsCertificate.Issuer, pThis->v.pWorkingIssuer)) + return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_ISSUER_MISMATCH, "Issuer mismatch"); + + return true; +} + + +/** + * Step 6.1.3.b-c. + */ +static bool rtCrX509CpvCheckNameConstraints(PRTCRX509CERTPATHSINT pThis, PRTCRX509CERTPATHNODE pNode) +{ + if (pThis->v.fNoPermittedSubtrees) + return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_NO_PERMITTED_NAMES, "No permitted subtrees"); + + if ( pNode->pCert->TbsCertificate.Subject.cItems > 0 + && ( !rtCrX509CpvIsNamePermitted(pThis, &pNode->pCert->TbsCertificate.Subject) + || rtCrX509CpvIsNameExcluded(pThis, &pNode->pCert->TbsCertificate.Subject)) ) + return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_NAME_NOT_PERMITTED, + "Subject name is not permitted by current name constraints"); + + PCRTCRX509GENERALNAMES pAltSubjectName = pNode->pCert->TbsCertificate.T3.pAltSubjectName; + if (pAltSubjectName) + { + uint32_t i = pAltSubjectName->cItems; + while (i-- > 0) + if ( !rtCrX509CpvIsGeneralNamePermitted(pThis, pAltSubjectName->papItems[i]) + || rtCrX509CpvIsGeneralNameExcluded(pThis, pAltSubjectName->papItems[i])) + return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_ALT_NAME_NOT_PERMITTED, + "Alternative name #%u is is not permitted by current name constraints", i); + } + + return true; +} + + +/** + * Step 6.1.3.d-f. + */ +static bool rtCrX509CpvWorkValidPolicyTree(PRTCRX509CERTPATHSINT pThis, uint32_t iDepth, PRTCRX509CERTPATHNODE pNode, + bool fSelfIssued) +{ + PCRTCRX509CERTIFICATEPOLICIES pPolicies = pNode->pCert->TbsCertificate.T3.pCertificatePolicies; + if (pPolicies) + { + /* + * 6.1.3.d.1 - Work the certiciate policies into the tree. + */ + PRTCRX509CERTPATHSPOLICYNODE pCur; + PRTLISTANCHOR pListAbove = &pThis->v.paValidPolicyDepthLists[iDepth - 1]; + uint32_t iAnyPolicy = UINT32_MAX; + uint32_t i = pPolicies->cItems; + while (i-- > 0) + { + PCRTCRX509POLICYQUALIFIERINFOS const pQualifiers = &pPolicies->papItems[i]->PolicyQualifiers; + PCRTASN1OBJID const pIdP = &pPolicies->papItems[i]->PolicyIdentifier; + if (RTAsn1ObjId_CompareWithString(pIdP, RTCRX509_ID_CE_CP_ANY_POLICY_OID) == 0) + { + iAnyPolicy++; + continue; + } + + /* + * 6.1.3.d.1.i - Create children for matching policies. + */ + uint32_t cMatches = 0; + RTListForEach(pListAbove, pCur, RTCRX509CERTPATHSPOLICYNODE, DepthEntry) + { + bool fMatch = RTAsn1ObjId_Compare(pCur->pExpectedPolicyFirst, pIdP) == 0; + if (!fMatch && pCur->cMoreExpectedPolicySet) + for (uint32_t j = 0; !fMatch && j < pCur->cMoreExpectedPolicySet; j++) + fMatch = RTAsn1ObjId_Compare(pCur->papMoreExpectedPolicySet[j], pIdP) == 0; + if (fMatch) + { + if (!rtCrX509CpvPolicyTreeInsertNew(pThis, pCur, iDepth, pIdP, pQualifiers, pIdP)) + return false; + cMatches++; + } + } + + /* + * 6.1.3.d.1.ii - If no matches above do the same for anyPolicy + * nodes, only match with valid policy this time. + */ + if (cMatches == 0) + { + RTListForEach(pListAbove, pCur, RTCRX509CERTPATHSPOLICYNODE, DepthEntry) + { + if (RTAsn1ObjId_CompareWithString(pCur->pExpectedPolicyFirst, RTCRX509_ID_CE_CP_ANY_POLICY_OID) == 0) + { + if (!rtCrX509CpvPolicyTreeInsertNew(pThis, pCur, iDepth, pIdP, pQualifiers, pIdP)) + return false; + } + } + } + } + + /* + * 6.1.3.d.2 - If anyPolicy present, make sure all expected policies + * are propagated to the current depth. + */ + if ( iAnyPolicy < pPolicies->cItems + && ( pThis->v.cInhibitAnyPolicy > 0 + || (pNode->pParent && fSelfIssued) ) ) + { + PCRTCRX509POLICYQUALIFIERINFOS pApQ = &pPolicies->papItems[iAnyPolicy]->PolicyQualifiers; + RTListForEach(pListAbove, pCur, RTCRX509CERTPATHSPOLICYNODE, DepthEntry) + { + if (!rtCrX509CpvPolicyTreeIsChild(pCur, pCur->pExpectedPolicyFirst)) + rtCrX509CpvPolicyTreeInsertNew(pThis, pCur, iDepth, pCur->pExpectedPolicyFirst, pApQ, + pCur->pExpectedPolicyFirst); + for (uint32_t j = 0; j < pCur->cMoreExpectedPolicySet; j++) + if (!rtCrX509CpvPolicyTreeIsChild(pCur, pCur->papMoreExpectedPolicySet[j])) + rtCrX509CpvPolicyTreeInsertNew(pThis, pCur, iDepth, pCur->papMoreExpectedPolicySet[j], pApQ, + pCur->papMoreExpectedPolicySet[j]); + } + } + /* + * 6.1.3.d.3 - Prune the tree. + */ + else + rtCrX509CpvPolicyTreePrune(pThis, iDepth - 1); + } + else + { + /* + * 6.1.3.e - No policy extension present, set tree to NULL. + */ + rtCrX509CpvPolicyTreeDestroy(pThis); + } + + /* + * 6.1.3.f - NULL tree check. + */ + if ( pThis->v.pValidPolicyTree == NULL + && pThis->v.cExplicitPolicy == 0) + return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_NO_VALID_POLICY, + "An explicit policy is called for but the valid policy tree is NULL."); + return RT_SUCCESS(pThis->rc); +} + + +/** + * Step 6.1.4.a-b. + */ +static bool rtCrX509CpvSoakUpPolicyMappings(PRTCRX509CERTPATHSINT pThis, uint32_t iDepth, + PCRTCRX509POLICYMAPPINGS pPolicyMappings) +{ + /* + * 6.1.4.a - The anyPolicy is not allowed in policy mappings as it would + * allow an evil intermediate certificate to expand the policy + * scope of a certiciate chain without regard to upstream. + */ + uint32_t i = pPolicyMappings->cItems; + while (i-- > 0) + { + PCRTCRX509POLICYMAPPING const pOne = pPolicyMappings->papItems[i]; + if (RTAsn1ObjId_CompareWithString(&pOne->IssuerDomainPolicy, RTCRX509_ID_CE_CP_ANY_POLICY_OID) == 0) + return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_INVALID_POLICY_MAPPING, + "Invalid policy mapping %#u: IssuerDomainPolicy is anyPolicy.", i); + + if (RTAsn1ObjId_CompareWithString(&pOne->SubjectDomainPolicy, RTCRX509_ID_CE_CP_ANY_POLICY_OID) == 0) + return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_INVALID_POLICY_MAPPING, + "Invalid policy mapping %#u: SubjectDomainPolicy is anyPolicy.", i); + } + + PRTCRX509CERTPATHSPOLICYNODE pCur, pNext; + if (pThis->v.cInhibitPolicyMapping > 0) + { + /* + * 6.1.4.b.1 - Do the policy mapping. + */ + i = pPolicyMappings->cItems; + while (i-- > 0) + { + PCRTCRX509POLICYMAPPING const pOne = pPolicyMappings->papItems[i]; + + uint32_t cFound = 0; + RTListForEach(&pThis->v.paValidPolicyDepthLists[iDepth], pCur, RTCRX509CERTPATHSPOLICYNODE, DepthEntry) + { + if (RTAsn1ObjId_Compare(pCur->pValidPolicy, &pOne->IssuerDomainPolicy)) + { + if (!pCur->fAlreadyMapped) + { + pCur->fAlreadyMapped = true; + pCur->pExpectedPolicyFirst = &pOne->SubjectDomainPolicy; + } + else + { + uint32_t iExpected = pCur->cMoreExpectedPolicySet; + void *pvNew = RTMemRealloc(pCur->papMoreExpectedPolicySet, + sizeof(pCur->papMoreExpectedPolicySet[0]) * (iExpected + 1)); + if (!pvNew) + return rtCrX509CpvFailed(pThis, VERR_NO_MEMORY, + "Error growing papMoreExpectedPolicySet array (cur %u, depth %u)", + pCur->cMoreExpectedPolicySet, iDepth); + pCur->papMoreExpectedPolicySet = (PCRTASN1OBJID *)pvNew; + pCur->papMoreExpectedPolicySet[iExpected] = &pOne->SubjectDomainPolicy; + pCur->cMoreExpectedPolicySet = iExpected + 1; + } + cFound++; + } + } + + /* + * If no mapping took place, look for an anyPolicy node. + */ + if (!cFound) + { + RTListForEach(&pThis->v.paValidPolicyDepthLists[iDepth], pCur, RTCRX509CERTPATHSPOLICYNODE, DepthEntry) + { + if (RTAsn1ObjId_CompareWithString(pCur->pValidPolicy, RTCRX509_ID_CE_CP_ANY_POLICY_OID) == 0) + { + if (!rtCrX509CpvPolicyTreeInsertNew(pThis, pCur->pParent, iDepth, + &pOne->IssuerDomainPolicy, + pCur->pPolicyQualifiers, + &pOne->SubjectDomainPolicy)) + return false; + break; + } + } + } + } + } + else + { + /* + * 6.1.4.b.2 - Remove matching policies from the tree if mapping is + * inhibited and prune the tree. + */ + uint32_t cRemoved = 0; + i = pPolicyMappings->cItems; + while (i-- > 0) + { + PCRTCRX509POLICYMAPPING const pOne = pPolicyMappings->papItems[i]; + RTListForEachSafe(&pThis->v.paValidPolicyDepthLists[iDepth], pCur, pNext, RTCRX509CERTPATHSPOLICYNODE, DepthEntry) + { + if (RTAsn1ObjId_Compare(pCur->pValidPolicy, &pOne->IssuerDomainPolicy)) + { + rtCrX509CpvPolicyTreeDestroyNode(pThis, pCur); + cRemoved++; + } + } + } + if (cRemoved) + rtCrX509CpvPolicyTreePrune(pThis, iDepth - 1); + } + + return true; +} + + +/** + * Step 6.1.4.d-f & 6.1.5.c-e. + */ +static void rtCrX509CpvSetWorkingPublicKeyInfo(PRTCRX509CERTPATHSINT pThis, PRTCRX509CERTPATHNODE pNode) +{ + PCRTCRX509TBSCERTIFICATE const pTbsCert = &pNode->pCert->TbsCertificate; + + /* + * 6.1.4.d - The public key. + */ + pThis->v.pWorkingPublicKey = &pTbsCert->SubjectPublicKeyInfo.SubjectPublicKey; + + /* + * 6.1.4.e - The public key parameters. Use new ones if present, keep old + * if the algorithm remains the same. + */ + if ( RTASN1CORE_IS_PRESENT(&pTbsCert->SubjectPublicKeyInfo.Algorithm.Parameters.u.Core) + && pTbsCert->SubjectPublicKeyInfo.Algorithm.Parameters.enmType != RTASN1TYPE_NULL) + pThis->v.pWorkingPublicKeyParameters = &pTbsCert->SubjectPublicKeyInfo.Algorithm.Parameters; + else if ( pThis->v.pWorkingPublicKeyParameters + && RTAsn1ObjId_Compare(pThis->v.pWorkingPublicKeyAlgorithm, &pTbsCert->SubjectPublicKeyInfo.Algorithm.Algorithm) != 0) + pThis->v.pWorkingPublicKeyParameters = NULL; + + /* + * 6.1.4.f - The public algorithm. + */ + pThis->v.pWorkingPublicKeyAlgorithm = &pTbsCert->SubjectPublicKeyInfo.Algorithm.Algorithm; +} + + +/** + * Step 6.1.4.g. + */ +static bool rtCrX509CpvSoakUpNameConstraints(PRTCRX509CERTPATHSINT pThis, PCRTCRX509NAMECONSTRAINTS pNameConstraints) +{ + if (pNameConstraints->T0.PermittedSubtrees.cItems > 0) + if (!rtCrX509CpvIntersectionPermittedSubtrees(pThis, &pNameConstraints->T0.PermittedSubtrees)) + return false; + + if (pNameConstraints->T1.ExcludedSubtrees.cItems > 0) + if (!rtCrX509CpvAddExcludedSubtrees(pThis, &pNameConstraints->T1.ExcludedSubtrees)) + return false; + + return true; +} + + +/** + * Step 6.1.4.i. + */ +static bool rtCrX509CpvSoakUpPolicyConstraints(PRTCRX509CERTPATHSINT pThis, PCRTCRX509POLICYCONSTRAINTS pPolicyConstraints) +{ + if (RTAsn1Integer_IsPresent(&pPolicyConstraints->RequireExplicitPolicy)) + { + if (RTAsn1Integer_UnsignedCompareWithU32(&pPolicyConstraints->RequireExplicitPolicy, pThis->v.cExplicitPolicy) < 0) + pThis->v.cExplicitPolicy = pPolicyConstraints->RequireExplicitPolicy.uValue.s.Lo; + } + + if (RTAsn1Integer_IsPresent(&pPolicyConstraints->InhibitPolicyMapping)) + { + if (RTAsn1Integer_UnsignedCompareWithU32(&pPolicyConstraints->InhibitPolicyMapping, pThis->v.cInhibitPolicyMapping) < 0) + pThis->v.cInhibitPolicyMapping = pPolicyConstraints->InhibitPolicyMapping.uValue.s.Lo; + } + return true; +} + + +/** + * Step 6.1.4.j. + */ +static bool rtCrX509CpvSoakUpInhibitAnyPolicy(PRTCRX509CERTPATHSINT pThis, PCRTASN1INTEGER pInhibitAnyPolicy) +{ + if (RTAsn1Integer_UnsignedCompareWithU32(pInhibitAnyPolicy, pThis->v.cInhibitAnyPolicy) < 0) + pThis->v.cInhibitAnyPolicy = pInhibitAnyPolicy->uValue.s.Lo; + return true; +} + + +/** + * Steps 6.1.4.k, 6.1.4.l, 6.1.4.m, and 6.1.4.n. + */ +static bool rtCrX509CpvCheckAndSoakUpBasicConstraintsAndKeyUsage(PRTCRX509CERTPATHSINT pThis, PRTCRX509CERTPATHNODE pNode, + bool fSelfIssued) +{ + /* 6.1.4.k - If basic constraints present, CA must be set. */ + if (RTAsn1Integer_UnsignedCompareWithU32(&pNode->pCert->TbsCertificate.T0.Version, RTCRX509TBSCERTIFICATE_V3) != 0) + { + /* Note! Add flags if support for older certificates is needed later. */ + return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_NOT_V3_CERT, + "Only version 3 certificates are supported (Version=%llu)", + pNode->pCert->TbsCertificate.T0.Version.uValue); + } + PCRTCRX509BASICCONSTRAINTS pBasicConstraints = pNode->pCert->TbsCertificate.T3.pBasicConstraints; + if (pBasicConstraints) + { + if (!pBasicConstraints->CA.fValue) + return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_NOT_CA_CERT, + "Intermediate certificate (#%u) is not marked as a CA", pThis->v.iNode); + } + + /* 6.1.4.l - Work cMaxPathLength. */ + if (!fSelfIssued) + { + if (pThis->v.cMaxPathLength > 0) + pThis->v.cMaxPathLength--; + else + return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_MAX_PATH_LENGTH, + "Hit max path length at node #%u", pThis->v.iNode); + } + + /* 6.1.4.m - Update cMaxPathLength if basic constrain field is present and smaller. */ + if (pBasicConstraints) + { + if (RTAsn1Integer_IsPresent(&pBasicConstraints->PathLenConstraint)) + if (RTAsn1Integer_UnsignedCompareWithU32(&pBasicConstraints->PathLenConstraint, pThis->v.cMaxPathLength) < 0) + pThis->v.cMaxPathLength = pBasicConstraints->PathLenConstraint.uValue.s.Lo; + } + + /* 6.1.4.n - Require keyCertSign in key usage if the extension is present. */ + PCRTCRX509TBSCERTIFICATE const pTbsCert = &pNode->pCert->TbsCertificate; + if ( (pTbsCert->T3.fFlags & RTCRX509TBSCERTIFICATE_F_PRESENT_KEY_USAGE) + && !(pTbsCert->T3.fKeyUsage & RTCRX509CERT_KEY_USAGE_F_KEY_CERT_SIGN)) + return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_MISSING_KEY_CERT_SIGN, + "Node #%u does not have KeyCertSign set (keyUsage=%#x)", + pThis->v.iNode, pTbsCert->T3.fKeyUsage); + + return true; +} + + +/** + * Step 6.1.4.o - check out critical extensions. + */ +static bool rtCrX509CpvCheckCriticalExtensions(PRTCRX509CERTPATHSINT pThis, PRTCRX509CERTPATHNODE pNode) +{ + uint32_t cLeft = pNode->pCert->TbsCertificate.T3.Extensions.cItems; + PRTCRX509EXTENSION const *ppCur = pNode->pCert->TbsCertificate.T3.Extensions.papItems; + while (cLeft-- > 0) + { + PCRTCRX509EXTENSION const pCur = *ppCur; + if (pCur->Critical.fValue) + { + if ( RTAsn1ObjId_CompareWithString(&pCur->ExtnId, RTCRX509_ID_CE_KEY_USAGE_OID) != 0 + && RTAsn1ObjId_CompareWithString(&pCur->ExtnId, RTCRX509_ID_CE_SUBJECT_ALT_NAME_OID) != 0 + && RTAsn1ObjId_CompareWithString(&pCur->ExtnId, RTCRX509_ID_CE_ISSUER_ALT_NAME_OID) != 0 + && RTAsn1ObjId_CompareWithString(&pCur->ExtnId, RTCRX509_ID_CE_BASIC_CONSTRAINTS_OID) != 0 + && RTAsn1ObjId_CompareWithString(&pCur->ExtnId, RTCRX509_ID_CE_NAME_CONSTRAINTS_OID) != 0 + && RTAsn1ObjId_CompareWithString(&pCur->ExtnId, RTCRX509_ID_CE_CERTIFICATE_POLICIES_OID) != 0 + && RTAsn1ObjId_CompareWithString(&pCur->ExtnId, RTCRX509_ID_CE_POLICY_MAPPINGS_OID) != 0 + && RTAsn1ObjId_CompareWithString(&pCur->ExtnId, RTCRX509_ID_CE_POLICY_CONSTRAINTS_OID) != 0 + && RTAsn1ObjId_CompareWithString(&pCur->ExtnId, RTCRX509_ID_CE_EXT_KEY_USAGE_OID) != 0 + && RTAsn1ObjId_CompareWithString(&pCur->ExtnId, RTCRX509_ID_CE_INHIBIT_ANY_POLICY_OID) != 0 + && RTAsn1ObjId_CompareWithString(&pCur->ExtnId, RTCR_APPLE_CS_DEVID_APPLICATION_OID) != 0 + && RTAsn1ObjId_CompareWithString(&pCur->ExtnId, RTCR_APPLE_CS_DEVID_INSTALLER_OID) != 0 + && RTAsn1ObjId_CompareWithString(&pCur->ExtnId, RTCR_APPLE_CS_DEVID_KEXT_OID) != 0 + && RTAsn1ObjId_CompareWithString(&pCur->ExtnId, RTCR_APPLE_CS_DEVID_IPHONE_SW_DEV_OID) != 0 + && RTAsn1ObjId_CompareWithString(&pCur->ExtnId, RTCR_APPLE_CS_DEVID_MAC_SW_DEV_OID) != 0 + ) + { + /* @bugref{10130}: An IntelGraphicsPE2021 cert issued by iKG_AZSKGFDCS has a critical subjectKeyIdentifier + which we quietly ignore here. RFC-5280 conforming CAs should not mark this as critical. + On an end entity this extension can have relevance to path construction. */ + if ( pNode->uSrc == RTCRX509CERTPATHNODE_SRC_TARGET + && RTAsn1ObjId_CompareWithString(&pCur->ExtnId, RTCRX509_ID_CE_SUBJECT_KEY_IDENTIFIER_OID) == 0) + LogFunc(("Ignoring non-standard subjectKeyIdentifier on target certificate.\n")); + else + return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_UNKNOWN_CRITICAL_EXTENSION, + "Node #%u has an unknown critical extension: %s", + pThis->v.iNode, pCur->ExtnId.szObjId); + } + } + + ppCur++; + } + + return true; +} + + +/** + * Step 6.1.5 - The wrapping up. + */ +static bool rtCrX509CpvWrapUp(PRTCRX509CERTPATHSINT pThis, PRTCRX509CERTPATHNODE pNode) +{ + Assert(!pNode->pParent); Assert(pThis->pTarget == pNode->pCert); + + /* + * 6.1.5.a - Decrement explicit policy. + */ + if (pThis->v.cExplicitPolicy > 0) + pThis->v.cExplicitPolicy--; + + /* + * 6.1.5.b - Policy constraints and explicit policy. + */ + PCRTCRX509POLICYCONSTRAINTS pPolicyConstraints = pNode->pCert->TbsCertificate.T3.pPolicyConstraints; + if ( pPolicyConstraints + && RTAsn1Integer_IsPresent(&pPolicyConstraints->RequireExplicitPolicy) + && RTAsn1Integer_UnsignedCompareWithU32(&pPolicyConstraints->RequireExplicitPolicy, 0) == 0) + pThis->v.cExplicitPolicy = 0; + + /* + * 6.1.5.c-e - Update working public key info. + */ + rtCrX509CpvSetWorkingPublicKeyInfo(pThis, pNode); + + /* + * 6.1.5.f - Critical extensions. + */ + if (!rtCrX509CpvCheckCriticalExtensions(pThis, pNode)) + return false; + + /* + * 6.1.5.g - Calculate the intersection between the user initial policy set + * and the valid policy tree. + */ + rtCrX509CpvPolicyTreeIntersect(pThis, pThis->cInitialUserPolicySet, pThis->papInitialUserPolicySet); + + if ( pThis->v.cExplicitPolicy == 0 + && pThis->v.pValidPolicyTree == NULL) + return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_NO_VALID_POLICY, "No valid policy (wrap-up)."); + + return true; +} + + +/** + * Worker that validates one path. + * + * This implements the algorithm in RFC-5280, section 6.1, with exception of + * the CRL checks in 6.1.3.a.3. + * + * @returns success indicator. + * @param pThis The path builder & validator instance. + * @param pTrustAnchor The trust anchor node. + */ +static bool rtCrX509CpvOneWorker(PRTCRX509CERTPATHSINT pThis, PRTCRX509CERTPATHNODE pTrustAnchor) +{ + /* + * Init. + */ + rtCrX509CpvInit(pThis, pTrustAnchor); + if (RT_SUCCESS(pThis->rc)) + { + /* + * Maybe do some trust anchor checks. + */ + if (!rtCrX509CpvMaybeCheckTrustAnchor(pThis, pTrustAnchor)) + { + AssertStmt(RT_FAILURE_NP(pThis->rc), pThis->rc = VERR_CR_X509_CERTPATHS_INTERNAL_ERROR); + return false; + } + + /* + * Special case, target certificate is trusted. + */ + if (!pTrustAnchor->pParent) + return true; /* rtCrX509CpvWrapUp should not be needed here. */ + + /* + * Normal processing. + */ + PRTCRX509CERTPATHNODE pNode = pTrustAnchor->pParent; + uint32_t iNode = pThis->v.iNode = 1; /* We count to cNode (inclusive). Same a validation tree depth. */ + while (pNode && RT_SUCCESS(pThis->rc)) + { + /* + * Basic certificate processing. + */ + if (!rtCrX509CpvCheckBasicCertInfo(pThis, pNode)) /* Step 6.1.3.a */ + break; + + bool const fSelfIssued = rtCrX509CertPathsIsSelfIssued(pNode); + if (!fSelfIssued || !pNode->pParent) /* Step 6.1.3.b-c */ + if (!rtCrX509CpvCheckNameConstraints(pThis, pNode)) + break; + + if (!rtCrX509CpvWorkValidPolicyTree(pThis, iNode, pNode, fSelfIssued)) /* Step 6.1.3.d-f */ + break; + + /* + * If it's the last certificate in the path, do wrap-ups. + */ + if (!pNode->pParent) /* Step 6.1.5 */ + { + Assert(iNode == pThis->v.cNodes); + if (!rtCrX509CpvWrapUp(pThis, pNode)) + break; + AssertRCBreak(pThis->rc); + return true; + } + + /* + * Preparations for the next certificate. + */ + PCRTCRX509TBSCERTIFICATE const pTbsCert = &pNode->pCert->TbsCertificate; + if ( pTbsCert->T3.pPolicyMappings + && !rtCrX509CpvSoakUpPolicyMappings(pThis, iNode, pTbsCert->T3.pPolicyMappings)) /* Step 6.1.4.a-b */ + break; + + pThis->v.pWorkingIssuer = &pTbsCert->Subject; /* Step 6.1.4.c */ + + rtCrX509CpvSetWorkingPublicKeyInfo(pThis, pNode); /* Step 6.1.4.d-f */ + + if ( pTbsCert->T3.pNameConstraints /* Step 6.1.4.g */ + && !rtCrX509CpvSoakUpNameConstraints(pThis, pTbsCert->T3.pNameConstraints)) + break; + + if (!fSelfIssued) /* Step 6.1.4.h */ + { + if (pThis->v.cExplicitPolicy > 0) + pThis->v.cExplicitPolicy--; + if (pThis->v.cInhibitPolicyMapping > 0) + pThis->v.cInhibitPolicyMapping--; + if (pThis->v.cInhibitAnyPolicy > 0) + pThis->v.cInhibitAnyPolicy--; + } + + if ( pTbsCert->T3.pPolicyConstraints /* Step 6.1.4.j */ + && !rtCrX509CpvSoakUpPolicyConstraints(pThis, pTbsCert->T3.pPolicyConstraints)) + break; + + if ( pTbsCert->T3.pInhibitAnyPolicy /* Step 6.1.4.j */ + && !rtCrX509CpvSoakUpInhibitAnyPolicy(pThis, pTbsCert->T3.pInhibitAnyPolicy)) + break; + + if (!rtCrX509CpvCheckAndSoakUpBasicConstraintsAndKeyUsage(pThis, pNode, fSelfIssued)) /* Step 6.1.4.k-n */ + break; + + if (!rtCrX509CpvCheckCriticalExtensions(pThis, pNode)) /* Step 6.1.4.o */ + break; + + /* + * Advance to the next certificate. + */ + pNode = pNode->pParent; + pThis->v.iNode = ++iNode; + } + AssertStmt(RT_FAILURE_NP(pThis->rc), pThis->rc = VERR_CR_X509_CERTPATHS_INTERNAL_ERROR); + } + return false; +} + + +RTDECL(int) RTCrX509CertPathsValidateOne(RTCRX509CERTPATHS hCertPaths, uint32_t iPath, PRTERRINFO pErrInfo) +{ + /* + * Validate the input. + */ + PRTCRX509CERTPATHSINT pThis = hCertPaths; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRX509CERTPATHSINT_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(!(pThis->fFlags & ~RTCRX509CERTPATHSINT_F_VALID_MASK), VERR_INVALID_PARAMETER); + AssertPtrReturn(pThis->pTarget, VERR_INVALID_PARAMETER); + AssertPtrReturn(pThis->pRoot, VERR_INVALID_PARAMETER); + AssertReturn(pThis->rc == VINF_SUCCESS, VERR_INVALID_PARAMETER); + + /* + * Locate the path and validate it. + */ + int rc; + if (iPath < pThis->cPaths) + { + PRTCRX509CERTPATHNODE pLeaf = rtCrX509CertPathsGetLeafByIndex(pThis, iPath); + if (pLeaf) + { + if (RTCRX509CERTPATHNODE_SRC_IS_TRUSTED(pLeaf->uSrc)) + { + pThis->pErrInfo = pErrInfo; + rtCrX509CpvOneWorker(pThis, pLeaf); + pThis->pErrInfo = NULL; + rc = pThis->rc; + pThis->rc = VINF_SUCCESS; + } + else + rc = RTErrInfoSetF(pErrInfo, VERR_CR_X509_NO_TRUST_ANCHOR, "Path #%u is does not have a trust anchor: uSrc=%s", + iPath, rtCrX509CertPathsNodeGetSourceName(pLeaf)); + pLeaf->rcVerify = rc; + } + else + rc = VERR_CR_X509_CERTPATHS_INTERNAL_ERROR; + } + else + rc = VERR_NOT_FOUND; + return rc; +} + + +RTDECL(int) RTCrX509CertPathsValidateAll(RTCRX509CERTPATHS hCertPaths, uint32_t *pcValidPaths, PRTERRINFO pErrInfo) +{ + /* + * Validate the input. + */ + PRTCRX509CERTPATHSINT pThis = hCertPaths; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRX509CERTPATHSINT_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(!(pThis->fFlags & ~RTCRX509CERTPATHSINT_F_VALID_MASK), VERR_INVALID_PARAMETER); + AssertPtrReturn(pThis->pTarget, VERR_INVALID_PARAMETER); + AssertPtrReturn(pThis->pRoot, VERR_INVALID_PARAMETER); + AssertReturn(pThis->rc == VINF_SUCCESS, VERR_INVALID_PARAMETER); + AssertPtrNullReturn(pcValidPaths, VERR_INVALID_POINTER); + + /* + * Validate the paths. + */ + pThis->pErrInfo = pErrInfo; + + int rcLastFailure = VINF_SUCCESS; + uint32_t cValidPaths = 0; + PRTCRX509CERTPATHNODE pCurLeaf; + RTListForEach(&pThis->LeafList, pCurLeaf, RTCRX509CERTPATHNODE, ChildListOrLeafEntry) + { + if (RTCRX509CERTPATHNODE_SRC_IS_TRUSTED(pCurLeaf->uSrc)) + { + rtCrX509CpvOneWorker(hCertPaths, pCurLeaf); + if (RT_SUCCESS(pThis->rc)) + cValidPaths++; + else + rcLastFailure = pThis->rc; + pCurLeaf->rcVerify = pThis->rc; + pThis->rc = VINF_SUCCESS; + } + else + pCurLeaf->rcVerify = VERR_CR_X509_NO_TRUST_ANCHOR; + } + + pThis->pErrInfo = NULL; + + if (pcValidPaths) + *pcValidPaths = cValidPaths; + if (cValidPaths > 0) + return VINF_SUCCESS; + if (RT_SUCCESS_NP(rcLastFailure)) + return RTErrInfoSetF(pErrInfo, VERR_CR_X509_CPV_NO_TRUSTED_PATHS, + "None of the %u path(s) have a trust anchor.", pThis->cPaths); + return rcLastFailure; +} + + +RTDECL(uint32_t) RTCrX509CertPathsGetPathCount(RTCRX509CERTPATHS hCertPaths) +{ + /* + * Validate the input. + */ + PRTCRX509CERTPATHSINT pThis = hCertPaths; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTCRX509CERTPATHSINT_MAGIC, UINT32_MAX); + AssertPtrReturn(pThis->pRoot, UINT32_MAX); + + /* + * Return data. + */ + return pThis->cPaths; +} + + +RTDECL(int) RTCrX509CertPathsQueryPathInfo(RTCRX509CERTPATHS hCertPaths, uint32_t iPath, + bool *pfTrusted, uint32_t *pcNodes, PCRTCRX509NAME *ppSubject, + PCRTCRX509SUBJECTPUBLICKEYINFO *ppPublicKeyInfo, + PCRTCRX509CERTIFICATE *ppCert, PCRTCRCERTCTX *ppCertCtx, + int *prcVerify) +{ + /* + * Validate the input. + */ + PRTCRX509CERTPATHSINT pThis = hCertPaths; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRX509CERTPATHSINT_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pThis->pRoot, VERR_WRONG_ORDER); + AssertReturn(iPath < pThis->cPaths, VERR_NOT_FOUND); + + /* + * Get the data. + */ + PRTCRX509CERTPATHNODE pLeaf = rtCrX509CertPathsGetLeafByIndex(pThis, iPath); + AssertReturn(pLeaf, VERR_CR_X509_INTERNAL_ERROR); + + if (pfTrusted) + *pfTrusted = RTCRX509CERTPATHNODE_SRC_IS_TRUSTED(pLeaf->uSrc); + + if (pcNodes) + *pcNodes = pLeaf->uDepth + 1; /* Includes both trust anchor and target. */ + + if (ppSubject) + *ppSubject = pLeaf->pCert ? &pLeaf->pCert->TbsCertificate.Subject : &pLeaf->pCertCtx->pTaInfo->CertPath.TaName; + + if (ppPublicKeyInfo) + *ppPublicKeyInfo = pLeaf->pCert ? &pLeaf->pCert->TbsCertificate.SubjectPublicKeyInfo : &pLeaf->pCertCtx->pTaInfo->PubKey; + + if (ppCert) + *ppCert = pLeaf->pCert; + + if (ppCertCtx) + { + if (pLeaf->pCertCtx) + { + uint32_t cRefs = RTCrCertCtxRetain(pLeaf->pCertCtx); + AssertReturn(cRefs != UINT32_MAX, VERR_CR_X509_INTERNAL_ERROR); + } + *ppCertCtx = pLeaf->pCertCtx; + } + + if (prcVerify) + *prcVerify = pLeaf->rcVerify; + + return VINF_SUCCESS; +} + + +RTDECL(uint32_t) RTCrX509CertPathsGetPathLength(RTCRX509CERTPATHS hCertPaths, uint32_t iPath) +{ + /* + * Validate the input. + */ + PRTCRX509CERTPATHSINT pThis = hCertPaths; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTCRX509CERTPATHSINT_MAGIC, UINT32_MAX); + AssertPtrReturn(pThis->pRoot, UINT32_MAX); + AssertReturn(iPath < pThis->cPaths, UINT32_MAX); + + /* + * Get the data. + */ + PRTCRX509CERTPATHNODE pLeaf = rtCrX509CertPathsGetLeafByIndex(pThis, iPath); + AssertReturn(pLeaf, UINT32_MAX); + return pLeaf->uDepth + 1; +} + + +RTDECL(int) RTCrX509CertPathsGetPathVerifyResult(RTCRX509CERTPATHS hCertPaths, uint32_t iPath) +{ + /* + * Validate the input. + */ + PRTCRX509CERTPATHSINT pThis = hCertPaths; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTCRX509CERTPATHSINT_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pThis->pRoot, VERR_WRONG_ORDER); + AssertReturn(iPath < pThis->cPaths, VERR_NOT_FOUND); + + /* + * Get the data. + */ + PRTCRX509CERTPATHNODE pLeaf = rtCrX509CertPathsGetLeafByIndex(pThis, iPath); + AssertReturn(pLeaf, VERR_CR_X509_INTERNAL_ERROR); + + return pLeaf->rcVerify; +} + + +static PRTCRX509CERTPATHNODE rtCrX509CertPathsGetPathNodeByIndexes(PRTCRX509CERTPATHSINT pThis, uint32_t iPath, uint32_t iNode) +{ + PRTCRX509CERTPATHNODE pNode = rtCrX509CertPathsGetLeafByIndex(pThis, iPath); + Assert(pNode); + if (pNode) + { + if (iNode <= pNode->uDepth) + { + uint32_t uCertDepth = pNode->uDepth - iNode; + while (pNode->uDepth > uCertDepth) + pNode = pNode->pParent; + Assert(pNode); + Assert(pNode && pNode->uDepth == uCertDepth); + return pNode; + } + } + + return NULL; +} + + +RTDECL(PCRTCRX509CERTIFICATE) RTCrX509CertPathsGetPathNodeCert(RTCRX509CERTPATHS hCertPaths, uint32_t iPath, uint32_t iNode) +{ + /* + * Validate the input. + */ + PRTCRX509CERTPATHSINT pThis = hCertPaths; + AssertPtrReturn(pThis, NULL); + AssertReturn(pThis->u32Magic == RTCRX509CERTPATHSINT_MAGIC, NULL); + AssertPtrReturn(pThis->pRoot, NULL); + AssertReturn(iPath < pThis->cPaths, NULL); + + /* + * Get the data. + */ + PRTCRX509CERTPATHNODE pNode = rtCrX509CertPathsGetPathNodeByIndexes(pThis, iPath, iNode); + if (pNode) + return pNode->pCert; + return NULL; +} + + +/** @} */ + diff --git a/src/VBox/Runtime/common/crypto/x509-core.cpp b/src/VBox/Runtime/common/crypto/x509-core.cpp new file mode 100644 index 00000000..c1a0bb94 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/x509-core.cpp @@ -0,0 +1,1694 @@ +/* $Id: x509-core.cpp $ */ +/** @file + * IPRT - Crypto - X.509, Core APIs. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/x509.h> + +#include <iprt/err.h> +#include <iprt/string.h> +#include <iprt/uni.h> + +#include "x509-internal.h" + + +/* + * Generate the code. + */ +#include <iprt/asn1-generator-core.h> + + +/* + * X.509 Validity. + */ + +RTDECL(bool) RTCrX509Validity_IsValidAtTimeSpec(PCRTCRX509VALIDITY pThis, PCRTTIMESPEC pTimeSpec) +{ + if (RTAsn1Time_CompareWithTimeSpec(&pThis->NotBefore, pTimeSpec) > 0) + return false; + if (RTAsn1Time_CompareWithTimeSpec(&pThis->NotAfter, pTimeSpec) < 0) + return false; + return true; +} + + +/* + * One X.509 Algorithm Identifier. + */ + +RTDECL(RTDIGESTTYPE) RTCrX509AlgorithmIdentifier_QueryDigestType(PCRTCRX509ALGORITHMIDENTIFIER pThis) +{ + AssertPtrReturn(pThis, RTDIGESTTYPE_INVALID); + if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_MD5)) + return RTDIGESTTYPE_MD5; + if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA1)) + return RTDIGESTTYPE_SHA1; + if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA256)) + return RTDIGESTTYPE_SHA256; + if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA512)) + return RTDIGESTTYPE_SHA512; + + if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA384)) + return RTDIGESTTYPE_SHA384; + if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA224)) + return RTDIGESTTYPE_SHA224; + if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA512T224)) + return RTDIGESTTYPE_SHA512T224; + if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA512T256)) + return RTDIGESTTYPE_SHA512T256; + + if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA3_224)) + return RTDIGESTTYPE_SHA3_224; + if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA3_256)) + return RTDIGESTTYPE_SHA3_256; + if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA3_384)) + return RTDIGESTTYPE_SHA3_384; + if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA3_512)) + return RTDIGESTTYPE_SHA3_512; + return RTDIGESTTYPE_INVALID; +} + + +RTDECL(uint32_t) RTCrX509AlgorithmIdentifier_QueryDigestSize(PCRTCRX509ALGORITHMIDENTIFIER pThis) +{ + AssertPtrReturn(pThis, UINT32_MAX); + + /* common */ + if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_MD5)) + return 128 / 8; + if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA1)) + return 160 / 8; + if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA256)) + return 256 / 8; + if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA512)) + return 512 / 8; + + /* Less common. */ + if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_MD2)) + return 128 / 8; + if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_MD4)) + return 128 / 8; + if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA384)) + return 384 / 8; + if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA224)) + return 224 / 8; + if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA512T224)) + return 224 / 8; + if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA512T256)) + return 256 / 8; + if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA3_224)) + return 224 / 8; + if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA3_256)) + return 256 / 8; + if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA3_384)) + return 384 / 8; + if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_SHA3_512)) + return 512 / 8; + if (!strcmp(pThis->Algorithm.szObjId, RTCRX509ALGORITHMIDENTIFIERID_WHIRLPOOL)) + return 512 / 8; + + return UINT32_MAX; +} + + +RTDECL(int) RTCrX509AlgorithmIdentifier_CompareWithString(PCRTCRX509ALGORITHMIDENTIFIER pThis, const char *pszObjId) +{ + return strcmp(pThis->Algorithm.szObjId, pszObjId); +} + + +RTDECL(int) RTCrX509AlgorithmIdentifier_CompareDigestOidAndEncryptedDigestOid(const char *pszDigestOid, + const char *pszEncryptedDigestOid) +{ + /* common */ + if (!strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_MD5)) + { + if (!strcmp(pszEncryptedDigestOid, RTCRX509ALGORITHMIDENTIFIERID_MD5_WITH_RSA)) + return 0; + } + else if (!strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA1)) + { + if (!strcmp(pszEncryptedDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA1_WITH_RSA)) + return 0; + } + else if (!strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA256)) + { + if (!strcmp(pszEncryptedDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA256_WITH_RSA)) + return 0; + } + else if (!strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA512)) + { + if (!strcmp(pszEncryptedDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA512_WITH_RSA)) + return 0; + } + /* Less common. */ + else if (!strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_MD2)) + { + if (!strcmp(pszEncryptedDigestOid, RTCRX509ALGORITHMIDENTIFIERID_MD2_WITH_RSA)) + return 0; + } + else if (!strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_MD4)) + { + if (!strcmp(pszEncryptedDigestOid, RTCRX509ALGORITHMIDENTIFIERID_MD4_WITH_RSA)) + return 0; + } + else if (!strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA384)) + { + if (!strcmp(pszEncryptedDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA384_WITH_RSA)) + return 0; + } + else if (!strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA224)) + { + if (!strcmp(pszEncryptedDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA224_WITH_RSA)) + return 0; + } + else if (!strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA512T224)) + { + if (!strcmp(pszEncryptedDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA512T224_WITH_RSA)) + return 0; + } + else if (!strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA512T256)) + { + if (!strcmp(pszEncryptedDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA512T256_WITH_RSA)) + return 0; + } + else if (!strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA3_224)) + { + if (!strcmp(pszEncryptedDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA3_224_WITH_RSA)) + return 0; + } + else if (!strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA3_256)) + { + if (!strcmp(pszEncryptedDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA3_256_WITH_RSA)) + return 0; + } + else if (!strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA3_384)) + { + if (!strcmp(pszEncryptedDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA3_384_WITH_RSA)) + return 0; + } + else if (!strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA3_512)) + { + if (!strcmp(pszEncryptedDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA3_512_WITH_RSA)) + return 0; + } + else if (!strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_WHIRLPOOL)) + { + /* ?? */ + } + else + return -1; + return 1; +} + +RTDECL(int) RTCrX509AlgorithmIdentifier_CompareDigestAndEncryptedDigest(PCRTCRX509ALGORITHMIDENTIFIER pDigest, + PCRTCRX509ALGORITHMIDENTIFIER pEncryptedDigest) +{ + return RTCrX509AlgorithmIdentifier_CompareDigestOidAndEncryptedDigestOid(pDigest->Algorithm.szObjId, + pEncryptedDigest->Algorithm.szObjId); +} + + +RTDECL(const char *) RTCrX509AlgorithmIdentifier_CombineEncryptionOidAndDigestOid(const char *pszEncryptionOid, + const char *pszDigestOid) +{ + /* RSA: */ + if (!strcmp(pszEncryptionOid, RTCRX509ALGORITHMIDENTIFIERID_RSA)) + { + if ( !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_MD5) + || !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_MD5_WITH_RSA)) + return RTCRX509ALGORITHMIDENTIFIERID_MD5_WITH_RSA; + if ( !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA1) + || !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA1_WITH_RSA)) + return RTCRX509ALGORITHMIDENTIFIERID_SHA1_WITH_RSA; + if ( !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA256) + || !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA256_WITH_RSA)) + return RTCRX509ALGORITHMIDENTIFIERID_SHA256_WITH_RSA; + if ( !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA512) + || !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA512_WITH_RSA)) + return RTCRX509ALGORITHMIDENTIFIERID_SHA512_WITH_RSA; + if ( !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_MD2) + || !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_MD2_WITH_RSA)) + return RTCRX509ALGORITHMIDENTIFIERID_MD2_WITH_RSA; + if ( !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_MD4) + || !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_MD4_WITH_RSA)) + return RTCRX509ALGORITHMIDENTIFIERID_MD4_WITH_RSA; + if ( !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA384) + || !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA384_WITH_RSA)) + return RTCRX509ALGORITHMIDENTIFIERID_SHA384_WITH_RSA; + if ( !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA224) + || !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA224_WITH_RSA)) + return RTCRX509ALGORITHMIDENTIFIERID_SHA224_WITH_RSA; + if ( !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA512T224) + || !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA512T224_WITH_RSA)) + return RTCRX509ALGORITHMIDENTIFIERID_SHA512T224_WITH_RSA; + if ( !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA512T256) + || !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA512T256_WITH_RSA)) + return RTCRX509ALGORITHMIDENTIFIERID_SHA512T256_WITH_RSA; + if ( !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA3_224) + || !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA3_224_WITH_RSA)) + return RTCRX509ALGORITHMIDENTIFIERID_SHA3_224_WITH_RSA; + if ( !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA3_256) + || !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA3_256_WITH_RSA)) + return RTCRX509ALGORITHMIDENTIFIERID_SHA3_256_WITH_RSA; + if ( !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA3_384) + || !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA3_384_WITH_RSA)) + return RTCRX509ALGORITHMIDENTIFIERID_SHA3_384_WITH_RSA; + if ( !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA3_512) + || !strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_SHA3_512_WITH_RSA)) + return RTCRX509ALGORITHMIDENTIFIERID_SHA3_512_WITH_RSA; + + /* if (!strcmp(pszDigestOid, RTCRX509ALGORITHMIDENTIFIERID_WHIRLPOOL)) + return ???; */ + } + else if (RTCrX509AlgorithmIdentifier_CompareDigestOidAndEncryptedDigestOid(pszDigestOid, pszEncryptionOid) == 0) + return pszEncryptionOid; + + AssertMsgFailed(("enc=%s hash=%s\n", pszEncryptionOid, pszDigestOid)); + return NULL; +} + + +RTDECL(const char *) RTCrX509AlgorithmIdentifier_CombineEncryptionAndDigest(PCRTCRX509ALGORITHMIDENTIFIER pEncryption, + PCRTCRX509ALGORITHMIDENTIFIER pDigest) +{ + return RTCrX509AlgorithmIdentifier_CombineEncryptionOidAndDigestOid(pEncryption->Algorithm.szObjId, + pDigest->Algorithm.szObjId); +} + + +/* + * Set of X.509 Algorithm Identifiers. + */ + + +/* + * One X.509 AttributeTypeAndValue. + */ + + +/* + * Set of X.509 AttributeTypeAndValues / X.509 RelativeDistinguishedName. + */ + +/** + * Slow code path of rtCrX509CanNameIsNothing. + * + * @returns true if @uc maps to nothing, false if not. + * @param uc The unicode code point. + */ +static bool rtCrX509CanNameIsNothingSlow(RTUNICP uc) +{ + switch (uc) + { + /* 2.2 Map - Paragraph 1: */ + case 0x00ad: + case 0x1806: + case 0x034f: + case 0x180b: case 0x180c: case 0x180d: + + case 0xfe00: case 0xfe01: case 0xfe02: case 0xfe03: + case 0xfe04: case 0xfe05: case 0xfe06: case 0xfe07: + case 0xfe08: case 0xfe09: case 0xfe0a: case 0xfe0b: + case 0xfe0c: case 0xfe0d: case 0xfe0e: case 0xfe0f: + + case 0xfffc: + + /* 2.2 Map - Paragraph 3 (control code/function): */ + case 0x0000: case 0x0001: case 0x0002: case 0x0003: + case 0x0004: case 0x0005: case 0x0006: case 0x0007: + case 0x0008: + + case 0x000e: case 0x000f: + case 0x0010: case 0x0011: case 0x0012: case 0x0013: + case 0x0014: case 0x0015: case 0x0016: case 0x0017: + case 0x0018: case 0x0019: case 0x001a: case 0x001b: + case 0x001c: case 0x001d: case 0x001e: case 0x001f: + + case 0x007f: + case 0x0080: case 0x0081: case 0x0082: case 0x0083: + case 0x0084: /*case 0x0085:*/ case 0x0086: case 0x0087: + case 0x0088: case 0x0089: case 0x008a: case 0x008b: + case 0x008c: case 0x008d: case 0x008e: case 0x008f: + case 0x0090: case 0x0091: case 0x0092: case 0x0093: + case 0x0094: case 0x0095: case 0x0096: case 0x0097: + case 0x0098: case 0x0099: case 0x009a: case 0x009b: + case 0x009c: case 0x009d: case 0x009e: case 0x009f: + + case 0x06dd: + case 0x070f: + case 0x180e: + case 0x200c: case 0x200d: case 0x200e: case 0x200f: + case 0x202a: case 0x202b: case 0x202c: case 0x202d: case 0x202e: + case 0x2060: case 0x2061: case 0x2062: case 0x2063: + case 0x206a: case 0x206b: case 0x206c: case 0x206d: case 0x206e: case 0x206f: + case 0xfeff: + case 0xfff9: case 0xfffa: case 0xfffb: + case 0x1d173: case 0x1d174: case 0x1d175: case 0x1d176: case 0x1d177: case 0x1d178: case 0x1d179: case 0x1d17a: + case 0xe0001: + case 0xe0020: case 0xe0021: case 0xe0022: case 0xe0023: + case 0xe0024: case 0xe0025: case 0xe0026: case 0xe0027: + case 0xe0028: case 0xe0029: case 0xe002a: case 0xe002b: + case 0xe002c: case 0xe002d: case 0xe002e: case 0xe002f: + case 0xe0030: case 0xe0031: case 0xe0032: case 0xe0033: + case 0xe0034: case 0xe0035: case 0xe0036: case 0xe0037: + case 0xe0038: case 0xe0039: case 0xe003a: case 0xe003b: + case 0xe003c: case 0xe003d: case 0xe003e: case 0xe003f: + case 0xe0040: case 0xe0041: case 0xe0042: case 0xe0043: + case 0xe0044: case 0xe0045: case 0xe0046: case 0xe0047: + case 0xe0048: case 0xe0049: case 0xe004a: case 0xe004b: + case 0xe004c: case 0xe004d: case 0xe004e: case 0xe004f: + case 0xe0050: case 0xe0051: case 0xe0052: case 0xe0053: + case 0xe0054: case 0xe0055: case 0xe0056: case 0xe0057: + case 0xe0058: case 0xe0059: case 0xe005a: case 0xe005b: + case 0xe005c: case 0xe005d: case 0xe005e: case 0xe005f: + case 0xe0060: case 0xe0061: case 0xe0062: case 0xe0063: + case 0xe0064: case 0xe0065: case 0xe0066: case 0xe0067: + case 0xe0068: case 0xe0069: case 0xe006a: case 0xe006b: + case 0xe006c: case 0xe006d: case 0xe006e: case 0xe006f: + case 0xe0070: case 0xe0071: case 0xe0072: case 0xe0073: + case 0xe0074: case 0xe0075: case 0xe0076: case 0xe0077: + case 0xe0078: case 0xe0079: case 0xe007a: case 0xe007b: + case 0xe007c: case 0xe007d: case 0xe007e: case 0xe007f: + + /* 2.2 Map - Paragraph 4. */ + case 0x200b: + return true; + } + return false; +} + + +/** + * Checks if @a uc maps to nothing according to mapping rules of RFC-5280 and + * RFC-4518. + * + * @returns true if @uc maps to nothing, false if not. + * @param uc The unicode code point. + */ +DECLINLINE(bool) rtCrX509CanNameIsNothing(RTUNICP uc) +{ + if (uc > 0x001f && uc < 0x00ad) + return false; + return rtCrX509CanNameIsNothingSlow(uc); +} + + +/** + * Slow code path of rtCrX509CanNameIsSpace. + * + * @returns true if space, false if not. + * @param uc The unicode code point. + */ +static bool rtCrX509CanNameIsSpaceSlow(RTUNICP uc) +{ + switch (uc) + { + /* 2.2 Map - Paragraph 2. */ + case 0x09: + case 0x0a: + case 0x0b: + case 0x0c: + case 0x0d: + case 0x20: + case 0x0085: + case 0x00a0: + case 0x1680: + case 0x2000: case 0x2001: case 0x2002: case 0x2003: + case 0x2004: case 0x2005: case 0x2006: case 0x2007: + case 0x2008: case 0x2009: case 0x200a: + case 0x2028: case 0x2029: + case 0x202f: + case 0x205f: + case 0x3000: + return true; + } + return false; +} + + +/** + * Checks if @a uc is a space character according to the mapping rules of + * RFC-5280 and RFC-4518. + * + * @returns true if space, false if not. + * @param uc The unicode code point. + */ +DECLINLINE(bool) rtCrX509CanNameIsSpace(RTUNICP uc) +{ + if (uc < 0x0085) + { + if (uc > 0x0020) + return false; + if (uc == 0x0020) /* space */ + return true; + } + return rtCrX509CanNameIsSpaceSlow(uc); +} + + +static const char *rtCrX509CanNameStripLeft(const char *psz, size_t *pcch) +{ + /* + * Return space when we've encountered the first non-space-non-nothing code point. + */ + const char * const pszStart = psz; + const char *pszPrev; + for (;;) + { + pszPrev = psz; + RTUNICP uc; + int rc = RTStrGetCpEx(&psz, &uc); + AssertRCBreak(rc); + if (!uc) + { + if ((uintptr_t)(pszPrev - pszStart) >= *pcch) + break; + /* NUL inside the string, maps to nothing => ignore it. */ + } + else if (!rtCrX509CanNameIsSpace(uc) && !rtCrX509CanNameIsNothing(uc)) + break; + } + *pcch -= (size_t)(pszPrev - pszStart); + return pszPrev; +} + + +static RTUNICP rtCrX509CanNameGetNextCpWithMappingSlowSpace(const char **ppsz, size_t *pcch) +{ + /* + * Return space when we've encountered the first non-space-non-nothing code point. + */ + RTUNICP uc; + const char *psz = *ppsz; + const char * const pszStart = psz; + const char *pszPrev; + for (;;) + { + pszPrev = psz; + int rc = RTStrGetCpEx(&psz, &uc); + AssertRCBreakStmt(rc, uc = 0x20); + if (!uc) + { + if ((uintptr_t)(pszPrev - pszStart) >= *pcch) + { + uc = 0; /* End of string: Ignore trailing spaces. */ + break; + } + /* NUL inside the string, maps to nothing => ignore it. */ + } + else if (!rtCrX509CanNameIsSpace(uc) && !rtCrX509CanNameIsNothing(uc)) + { + uc = 0x20; /* Return space before current char. */ + break; + } + } + + *ppsz = pszPrev; + *pcch -= (size_t)(pszPrev - pszStart); + return uc; +} + + +DECLINLINE(RTUNICP) rtCrX509CanNameGetNextCpIgnoreNul(const char **ppsz, size_t *pcch) +{ + while (*pcch > 0) + { + const char *psz = *ppsz; + RTUNICP uc = (RTUNICP)*psz; + if (uc < 0x80) + { + *pcch -= 1; + *ppsz = psz + 1; + } + else + { + int rc = RTStrGetCpEx(ppsz, &uc); + AssertRCReturn(rc, uc); + size_t cchCp = (size_t)(*ppsz - psz); + AssertReturn(cchCp <= *pcch, 0); + *pcch -= cchCp; + } + if (uc != 0) + return uc; + } + return 0; +} + + +static RTUNICP rtCrX509CanNameGetNextCpWithMappingSlowNothing(const char **ppsz, size_t *pcch) +{ + /* + * Return first code point which doesn't map to nothing. If we encounter + * a space, we defer to the mapping-after-space routine above. + */ + for (;;) + { + RTUNICP uc = rtCrX509CanNameGetNextCpIgnoreNul(ppsz, pcch); + if (rtCrX509CanNameIsSpace(uc)) + return rtCrX509CanNameGetNextCpWithMappingSlowSpace(ppsz, pcch); + if (!rtCrX509CanNameIsNothing(uc) || uc == 0) + return uc; + } +} + + +DECLINLINE(RTUNICP) rtCrX509CanNameGetNextCpWithMapping(const char **ppsz, size_t *pcch) +{ + RTUNICP uc = rtCrX509CanNameGetNextCpIgnoreNul(ppsz, pcch); + if (uc) + { + if (!rtCrX509CanNameIsSpace(uc)) + { + if (!rtCrX509CanNameIsNothing(uc)) + return uc; + return rtCrX509CanNameGetNextCpWithMappingSlowNothing(ppsz, pcch); + } + return rtCrX509CanNameGetNextCpWithMappingSlowSpace(ppsz, pcch); + } + return uc; +} + + +RTDECL(bool) RTCrX509AttributeTypeAndValue_MatchAsRdnByRfc5280(PCRTCRX509ATTRIBUTETYPEANDVALUE pLeft, + PCRTCRX509ATTRIBUTETYPEANDVALUE pRight) +{ + if (RTAsn1ObjId_Compare(&pLeft->Type, &pRight->Type) == 0) + { + /* + * Try for perfect match in case we get luck. + */ +#ifdef DEBUG_bird /* Want to test the complicated code path first */ + if (pLeft->Value.enmType != RTASN1TYPE_STRING || pRight->Value.enmType != RTASN1TYPE_STRING) +#endif + if (RTAsn1DynType_Compare(&pLeft->Value, &pRight->Value) == 0) + return true; + + /* + * If both are string types, we can compare them according to RFC-5280. + */ + if ( pLeft->Value.enmType == RTASN1TYPE_STRING + && pRight->Value.enmType == RTASN1TYPE_STRING) + { + size_t cchLeft; + const char *pszLeft; + int rc = RTAsn1String_QueryUtf8(&pLeft->Value.u.String, &pszLeft, &cchLeft); + if (RT_SUCCESS(rc)) + { + size_t cchRight; + const char *pszRight; + rc = RTAsn1String_QueryUtf8(&pRight->Value.u.String, &pszRight, &cchRight); + if (RT_SUCCESS(rc)) + { + /* + * Perform a simplified RFC-5280 comparsion. + * The algorithm as be relaxed on the following counts: + * 1. No unicode normalization. + * 2. Prohibited characters not checked for. + * 3. Bidirectional characters are not ignored. + */ + pszLeft = rtCrX509CanNameStripLeft(pszLeft, &cchLeft); + pszRight = rtCrX509CanNameStripLeft(pszRight, &cchRight); + while (*pszLeft && *pszRight) + { + RTUNICP ucLeft = rtCrX509CanNameGetNextCpWithMapping(&pszLeft, &cchLeft); + RTUNICP ucRight = rtCrX509CanNameGetNextCpWithMapping(&pszRight, &cchRight); + if (ucLeft != ucRight) + { + ucLeft = RTUniCpToLower(ucLeft); + ucRight = RTUniCpToLower(ucRight); + if (ucLeft != ucRight) + return false; + } + } + + return cchRight == 0 && cchLeft == 0; + } + } + } + } + return false; +} + + +RTDECL(bool) RTCrX509RelativeDistinguishedName_MatchByRfc5280(PCRTCRX509RELATIVEDISTINGUISHEDNAME pLeft, + PCRTCRX509RELATIVEDISTINGUISHEDNAME pRight) +{ + /* + * No match if the attribute count differs. + */ + uint32_t const cItems = pLeft->cItems; + if (cItems == pRight->cItems) + { + /* + * Compare each attribute, but don't insist on the same order nor + * bother checking for duplicates (too complicated). + */ + for (uint32_t iLeft = 0; iLeft < cItems; iLeft++) + { + PCRTCRX509ATTRIBUTETYPEANDVALUE pLeftAttr = pLeft->papItems[iLeft]; + bool fFound = false; + for (uint32_t iRight = 0; iRight < cItems; iRight++) + if (RTCrX509AttributeTypeAndValue_MatchAsRdnByRfc5280(pLeftAttr, pRight->papItems[iRight])) + { + fFound = true; + break; + } + if (!fFound) + return false; + } + return true; + } + return false; + +} + + +/* + * X.509 Name. + */ + +RTDECL(bool) RTCrX509Name_MatchByRfc5280(PCRTCRX509NAME pLeft, PCRTCRX509NAME pRight) +{ + uint32_t const cItems = pLeft->cItems; + if (cItems == pRight->cItems) + { + /* Require exact order. */ + for (uint32_t iRdn = 0; iRdn < cItems; iRdn++) + if (!RTCrX509RelativeDistinguishedName_MatchByRfc5280(pLeft->papItems[iRdn], pRight->papItems[iRdn])) + return false; + return true; + } + return false; +} + + +RTDECL(bool) RTCrX509Name_ConstraintMatch(PCRTCRX509NAME pConstraint, PCRTCRX509NAME pName) +{ + /* + * Check that the constraint is a prefix of the name. This means that + * the name must have at least as many components and the constraint. + */ + if (pName->cItems >= pConstraint->cItems) + { + /* + * Parallel crawl of the two RDNs arrays. + */ + for (uint32_t i = 0; pConstraint->cItems; i++) + { + PCRTCRX509RELATIVEDISTINGUISHEDNAME pConstrRdns = pConstraint->papItems[i]; + PCRTCRX509RELATIVEDISTINGUISHEDNAME pNameRdns = pName->papItems[i]; + + /* + * Walk the constraint attribute & value array. + */ + for (uint32_t iConstrAttrib = 0; iConstrAttrib < pConstrRdns->cItems; iConstrAttrib++) + { + PCRTCRX509ATTRIBUTETYPEANDVALUE pConstrAttrib = pConstrRdns->papItems[iConstrAttrib]; + + /* + * Find matching attribute & value in the name. + */ + bool fFound = false; + for (uint32_t iNameAttrib = 0; iNameAttrib < pNameRdns->cItems; iNameAttrib++) + if (RTCrX509AttributeTypeAndValue_MatchAsRdnByRfc5280(pConstrAttrib, pNameRdns->papItems[iNameAttrib])) + { + fFound = true; + break; + } + if (fFound) + return false; + } + } + return true; + } + return false; +} + + +/** + * Mapping between X.500 object IDs and short and long names. + * + * See RFC-1327, RFC-4519 ... + */ +static struct +{ + const char *pszOid; + const char *pszShortNm; + size_t cchShortNm; + const char *pszLongNm; +} const g_aRdnMap[] = +{ + { "0.9.2342.19200300.100.1.1", RT_STR_TUPLE("uid"), "userid" }, + { "0.9.2342.19200300.100.1.3", RT_STR_TUPLE("Mail"), "Rfc822Mailbox" }, + { "0.9.2342.19200300.100.1.25", RT_STR_TUPLE("DC"), "DomainComponent" }, + { "1.2.840.113549.1.9.1", RT_STR_TUPLE("Email") /*nonstandard*/,"EmailAddress" }, + { "2.5.4.3", RT_STR_TUPLE("CN"), "CommonName" }, + { "2.5.4.4", RT_STR_TUPLE("SN"), "Surname" }, + { "2.5.4.5", RT_STR_TUPLE("SRN") /*nonstandard*/, "SerialNumber" }, + { "2.5.4.6", RT_STR_TUPLE("C"), "CountryName" }, + { "2.5.4.7", RT_STR_TUPLE("L"), "LocalityName" }, + { "2.5.4.8", RT_STR_TUPLE("ST"), "StateOrProviceName" }, + { "2.5.4.9", RT_STR_TUPLE("street"), "Street" }, + { "2.5.4.10", RT_STR_TUPLE("O"), "OrganizationName" }, + { "2.5.4.11", RT_STR_TUPLE("OU"), "OrganizationalUnitName" }, + { "2.5.4.12", RT_STR_TUPLE("title"), "Title" }, + { "2.5.4.13", RT_STR_TUPLE("desc"), "Description" }, + { "2.5.4.15", RT_STR_TUPLE("BC") /*nonstandard*/, "BusinessCategory" }, + { "2.5.4.17", RT_STR_TUPLE("ZIP") /*nonstandard*/, "PostalCode" }, + { "2.5.4.18", RT_STR_TUPLE("POBox") /*nonstandard*/,"PostOfficeBox" }, + { "2.5.4.20", RT_STR_TUPLE("PN") /*nonstandard*/, "TelephoneNumber" }, + { "2.5.4.33", RT_STR_TUPLE("RO") /*nonstandard*/, "RoleOccupant" }, + { "2.5.4.34", RT_STR_TUPLE("SA") /*nonstandard*/, "StreetAddress" }, + { "2.5.4.41", RT_STR_TUPLE("N") /*nonstandard*/, "Name" }, + { "2.5.4.42", RT_STR_TUPLE("GN"), "GivenName" }, + { "2.5.4.43", RT_STR_TUPLE("I") /*nonstandard*/, "Initials" }, + { "2.5.4.44", RT_STR_TUPLE("GQ") /*nonstandard*/, "GenerationQualifier" }, + { "2.5.4.46", RT_STR_TUPLE("DNQ") /*nonstandard*/, "DNQualifier" }, + { "2.5.4.51", RT_STR_TUPLE("HID") /*nonstandard*/, "HouseIdentifier" }, +}; + + +RTDECL(const char *) RTCrX509Name_GetShortRdn(PCRTASN1OBJID pRdnId) +{ + uint32_t iName = RT_ELEMENTS(g_aRdnMap); + while (iName-- > 0) + if (RTAsn1ObjId_CompareWithString(pRdnId, g_aRdnMap[iName].pszOid) == 0) + return g_aRdnMap[iName].pszShortNm; + return NULL; +} + + +RTDECL(bool) RTCrX509Name_MatchWithString(PCRTCRX509NAME pThis, const char *pszString) +{ + /* Keep track of the string length. */ + size_t cchString = strlen(pszString); + + /* + * The usual double loop for walking the components. + */ + for (uint32_t i = 0; i < pThis->cItems; i++) + { + PCRTCRX509RELATIVEDISTINGUISHEDNAME pRdn = pThis->papItems[i]; + for (uint32_t j = 0; j < pRdn->cItems; j++) + { + PCRTCRX509ATTRIBUTETYPEANDVALUE pComponent = pRdn->papItems[j]; + + /* + * Must be a string. + */ + if (pComponent->Value.enmType != RTASN1TYPE_STRING) + return false; + + /* + * Look up the component name prefix and check whether it's also in the string. + */ + uint32_t iName = RT_ELEMENTS(g_aRdnMap); + while (iName-- > 0) + if (RTAsn1ObjId_CompareWithString(&pComponent->Type, g_aRdnMap[iName].pszOid) == 0) + break; + AssertMsgReturn(iName != UINT32_MAX, ("Please extend g_aRdnMap with '%s'.\n", pComponent->Type.szObjId), false); + + if ( strncmp(pszString, g_aRdnMap[iName].pszShortNm, g_aRdnMap[iName].cchShortNm) != 0 + || pszString[g_aRdnMap[iName].cchShortNm] != '=') + return false; + + pszString += g_aRdnMap[iName].cchShortNm + 1; + cchString -= g_aRdnMap[iName].cchShortNm + 1; + + /* + * Compare the component string. + */ + size_t cchComponent; + int rc = RTAsn1String_QueryUtf8Len(&pComponent->Value.u.String, &cchComponent); + AssertRCReturn(rc, false); + + if (cchComponent > cchString) + return false; + if (RTAsn1String_CompareWithString(&pComponent->Value.u.String, pszString, cchComponent) != 0) + return false; + + cchString -= cchComponent; + pszString += cchComponent; + + /* + * Check separator comma + space and skip extra spaces before the next component. + */ + if (cchString) + { + if (pszString[0] != ',') + return false; + if (pszString[1] != ' ' && pszString[1] != '\t') + return false; + pszString += 2; + cchString -= 2; + + while (*pszString == ' ' || *pszString == '\t') + { + pszString++; + cchString--; + } + } + } + } + + /* + * If we got thru the whole name and the whole string, we're good. + */ + return *pszString == '\0'; +} + + +RTDECL(int) RTCrX509Name_FormatAsString(PCRTCRX509NAME pThis, char *pszBuf, size_t cbBuf, size_t *pcbActual) +{ + /* + * The usual double loop for walking the components. + */ + size_t off = 0; + int rc = VINF_SUCCESS; + for (uint32_t i = 0; i < pThis->cItems; i++) + { + PCRTCRX509RELATIVEDISTINGUISHEDNAME pRdn = pThis->papItems[i]; + for (uint32_t j = 0; j < pRdn->cItems; j++) + { + PCRTCRX509ATTRIBUTETYPEANDVALUE pComponent = pRdn->papItems[j]; + + /* + * Must be a string. + */ + if (pComponent->Value.enmType != RTASN1TYPE_STRING) + return VERR_CR_X509_NAME_NOT_STRING; + + /* + * Look up the component name prefix. + */ + uint32_t iName = RT_ELEMENTS(g_aRdnMap); + while (iName-- > 0) + if (RTAsn1ObjId_CompareWithString(&pComponent->Type, g_aRdnMap[iName].pszOid) == 0) + break; + AssertMsgReturn(iName != UINT32_MAX, ("Please extend g_aRdnMap with '%s'.\n", pComponent->Type.szObjId), + VERR_CR_X509_NAME_MISSING_RDN_MAP_ENTRY); + + /* + * Append the prefix. + */ + if (off) + { + if (off + 2 < cbBuf) + { + pszBuf[off] = ','; + pszBuf[off + 1] = ' '; + } + else + rc = VERR_BUFFER_OVERFLOW; + off += 2; + } + + if (off + g_aRdnMap[iName].cchShortNm + 1 < cbBuf) + { + memcpy(&pszBuf[off], g_aRdnMap[iName].pszShortNm, g_aRdnMap[iName].cchShortNm); + pszBuf[off + g_aRdnMap[iName].cchShortNm] = '='; + } + else + rc = VERR_BUFFER_OVERFLOW; + off += g_aRdnMap[iName].cchShortNm + 1; + + /* + * Add the component string. + */ + const char *pszUtf8; + size_t cchUtf8; + int rc2 = RTAsn1String_QueryUtf8(&pComponent->Value.u.String, &pszUtf8, &cchUtf8); + AssertRCReturn(rc2, rc2); + if (off + cchUtf8 < cbBuf) + memcpy(&pszBuf[off], pszUtf8, cchUtf8); + else + rc = VERR_BUFFER_OVERFLOW; + off += cchUtf8; + } + } + + if (pcbActual) + *pcbActual = off + 1; + if (off < cbBuf) + pszBuf[off] = '\0'; + return rc; +} + + + +/* + * One X.509 GeneralName. + */ + +/** + * Name constraint matching (RFC-5280): DNS Name. + * + * @returns true on match, false on mismatch. + * @param pConstraint The constraint name. + * @param pName The name to match against the constraint. + */ +static bool rtCrX509GeneralName_ConstraintMatchDnsName(PCRTCRX509GENERALNAME pConstraint, PCRTCRX509GENERALNAME pName) +{ + /* + * Empty constraint string is taken to match everything. + */ + if (pConstraint->u.pT2_DnsName->Asn1Core.cb == 0) + return true; + + /* + * Get the UTF-8 strings for the two. + */ + size_t cchConstraint; + char const *pszConstraint; + int rc = RTAsn1String_QueryUtf8(pConstraint->u.pT2_DnsName, &pszConstraint, &cchConstraint); + if (RT_SUCCESS(rc)) + { + size_t cchFull; + char const *pszFull; + rc = RTAsn1String_QueryUtf8(pName->u.pT2_DnsName, &pszFull, &cchFull); + if (RT_SUCCESS(rc)) + { + /* + * No match if the constraint is longer. + */ + if (cchConstraint > cchFull) + return false; + + /* + * No match if the constraint and name tail doesn't match + * in a case-insensitive compare. + */ + size_t offFull = cchFull - cchConstraint; + if (RTStrICmp(&pszFull[offFull], pszConstraint) != 0) + return false; + if (!offFull) + return true; + + /* + * The matching constraint must be delimited by a dot in the full + * name. There seems to be some discussion whether ".oracle.com" + * should match "www..oracle.com". This implementation does choose + * to not succeed in that case. + */ + if ((pszFull[offFull - 1] == '.') ^ (pszFull[offFull] == '.')) + return true; + + return false; + } + } + + /* fall back. */ + return RTCrX509GeneralName_Compare(pConstraint, pName) == 0; +} + + +/** + * Name constraint matching (RFC-5280): RFC-822 (email). + * + * @returns true on match, false on mismatch. + * @param pConstraint The constraint name. + * @param pName The name to match against the constraint. + */ +static bool rtCrX509GeneralName_ConstraintMatchRfc822Name(PCRTCRX509GENERALNAME pConstraint, PCRTCRX509GENERALNAME pName) +{ + /* + * Empty constraint string is taken to match everything. + */ + if (pConstraint->u.pT1_Rfc822->Asn1Core.cb == 0) + return true; + + /* + * Get the UTF-8 strings for the two. + */ + size_t cchConstraint; + char const *pszConstraint; + int rc = RTAsn1String_QueryUtf8(pConstraint->u.pT1_Rfc822, &pszConstraint, &cchConstraint); + if (RT_SUCCESS(rc)) + { + size_t cchFull; + char const *pszFull; + rc = RTAsn1String_QueryUtf8(pName->u.pT1_Rfc822, &pszFull, &cchFull); + if (RT_SUCCESS(rc)) + { + /* + * No match if the constraint is longer. + */ + if (cchConstraint > cchFull) + return false; + + /* + * A lone dot matches everything. + */ + if (cchConstraint == 1 && *pszConstraint == '.') + return true; + + /* + * If there is a '@' in the constraint, the entire address must match. + */ + const char *pszConstraintAt = (const char *)memchr(pszConstraint, '@', cchConstraint); + if (pszConstraintAt) + return cchConstraint == cchFull && RTStrICmp(pszConstraint, pszFull) == 0; + + /* + * No match if the constraint and name tail doesn't match + * in a case-insensitive compare. + */ + size_t offFull = cchFull - cchConstraint; + if (RTStrICmp(&pszFull[offFull], pszConstraint) != 0) + return false; + + /* + * If the constraint starts with a dot, we're supposed to be + * satisfied with a tail match. + */ + /** @todo Check if this should match even if offFull == 0. */ + if (*pszConstraint == '.') + return true; + + /* + * Otherwise, we require a hostname match and thus expect an '@' + * immediatly preceding the constraint match. + */ + if (pszFull[offFull - 1] == '@') + return true; + + return false; + } + } + + /* fall back. */ + return RTCrX509GeneralName_Compare(pConstraint, pName) == 0; +} + + +/** + * Extracts the hostname from an URI. + * + * @returns true if successfully extract, false if no hostname present. + * @param pszUri The URI. + * @param pchHostName . + * @param pcchHostName . + */ +static bool rtCrX509GeneralName_ExtractHostName(const char *pszUri, const char **pchHostName, size_t *pcchHostName) +{ + /* + * Skip the schema name. + */ + const char *pszStart = strchr(pszUri, ':'); + while (pszStart && (pszStart[1] != '/' || pszStart[2] != '/')) + pszStart = strchr(pszStart + 1, ':'); + if (pszStart) + { + pszStart += 3; + + /* + * The name ends with the first slash or ":port". + */ + const char *pszEnd = strchr(pszStart, '/'); + if (!pszEnd) + pszEnd = strchr(pszStart, '\0'); + if (memchr(pszStart, ':', (size_t)(pszEnd - pszStart))) + do + pszEnd--; + while (*pszEnd != ':'); + if (pszEnd != pszStart) + { + /* + * Drop access credentials at the front of the string if present. + */ + const char *pszAt = (const char *)memchr(pszStart, '@', (size_t)(pszEnd - pszStart)); + if (pszAt) + pszStart = pszAt + 1; + + /* + * If there is still some string left, that's the host name. + */ + if (pszEnd != pszStart) + { + *pcchHostName = (size_t)(pszEnd - pszStart); + *pchHostName = pszStart; + return true; + } + } + } + + *pcchHostName = 0; + *pchHostName = NULL; + return false; +} + + +/** + * Name constraint matching (RFC-5280): URI. + * + * @returns true on match, false on mismatch. + * @param pConstraint The constraint name. + * @param pName The name to match against the constraint. + */ +static bool rtCrX509GeneralName_ConstraintMatchUri(PCRTCRX509GENERALNAME pConstraint, PCRTCRX509GENERALNAME pName) +{ + /* + * Empty constraint string is taken to match everything. + */ + if (pConstraint->u.pT6_Uri->Asn1Core.cb == 0) + return true; + + /* + * Get the UTF-8 strings for the two. + */ + size_t cchConstraint; + char const *pszConstraint; + int rc = RTAsn1String_QueryUtf8(pConstraint->u.pT6_Uri, &pszConstraint, &cchConstraint); + if (RT_SUCCESS(rc)) + { + size_t cchFull; + char const *pszFull; + rc = RTAsn1String_QueryUtf8(pName->u.pT6_Uri, &pszFull, &cchFull); + if (RT_SUCCESS(rc)) + { + /* + * Isolate the hostname in the name. + */ + size_t cchHostName; + const char *pchHostName; + if (rtCrX509GeneralName_ExtractHostName(pszFull, &pchHostName, &cchHostName)) + { + /* + * Domain constraint. + */ + if (*pszConstraint == '.') + { + if (cchHostName >= cchConstraint) + { + size_t offHostName = cchHostName - cchConstraint; + if (RTStrICmp(&pchHostName[offHostName], pszConstraint) == 0) + { + /* "http://www..oracle.com" does not match ".oracle.com". + It's debatable whether "http://.oracle.com/" should match. */ + if ( !offHostName + || pchHostName[offHostName - 1] != '.') + return true; + } + } + } + /* + * Host name constraint. Full match required. + */ + else if ( cchHostName == cchConstraint + && RTStrNICmp(pchHostName, pszConstraint, cchHostName) == 0) + return true; + } + return false; + } + } + + /* fall back. */ + return RTCrX509GeneralName_Compare(pConstraint, pName) == 0; +} + + +/** + * Name constraint matching (RFC-5280): IP address. + * + * @returns true on match, false on mismatch. + * @param pConstraint The constraint name. + * @param pName The name to match against the constraint. + */ +static bool rtCrX509GeneralName_ConstraintMatchIpAddress(PCRTCRX509GENERALNAME pConstraint, PCRTCRX509GENERALNAME pName) +{ + uint8_t const *pbConstraint = pConstraint->u.pT7_IpAddress->Asn1Core.uData.pu8; + uint8_t const *pbFull = pName->u.pT7_IpAddress->Asn1Core.uData.pu8; + + /* + * IPv4. + */ + if ( pConstraint->u.pT7_IpAddress->Asn1Core.cb == 8 /* ip+netmask*/ + && pName->u.pT7_IpAddress->Asn1Core.cb == 4) /* ip */ + return ((pbFull[0] ^ pbConstraint[0]) & pbConstraint[4]) == 0 + && ((pbFull[1] ^ pbConstraint[1]) & pbConstraint[5]) == 0 + && ((pbFull[2] ^ pbConstraint[2]) & pbConstraint[6]) == 0 + && ((pbFull[3] ^ pbConstraint[3]) & pbConstraint[7]) == 0; + + /* + * IPv6. + */ + if ( pConstraint->u.pT7_IpAddress->Asn1Core.cb == 32 /* ip+netmask*/ + && pName->u.pT7_IpAddress->Asn1Core.cb == 16) /* ip */ + { + for (uint32_t i = 0; i < 16; i++) + if (((pbFull[i] ^ pbConstraint[i]) & pbConstraint[i + 16]) != 0) + return false; + return true; + } + + return RTCrX509GeneralName_Compare(pConstraint, pName) == 0; +} + + +RTDECL(bool) RTCrX509GeneralName_ConstraintMatch(PCRTCRX509GENERALNAME pConstraint, PCRTCRX509GENERALNAME pName) +{ + if (pConstraint->enmChoice == pName->enmChoice) + { + if (RTCRX509GENERALNAME_IS_DIRECTORY_NAME(pConstraint)) + return RTCrX509Name_ConstraintMatch(&pConstraint->u.pT4->DirectoryName, &pName->u.pT4->DirectoryName); + + if (RTCRX509GENERALNAME_IS_DNS_NAME(pConstraint)) + return rtCrX509GeneralName_ConstraintMatchDnsName(pConstraint, pName); + + if (RTCRX509GENERALNAME_IS_RFC822_NAME(pConstraint)) + return rtCrX509GeneralName_ConstraintMatchRfc822Name(pConstraint, pName); + + if (RTCRX509GENERALNAME_IS_URI(pConstraint)) + return rtCrX509GeneralName_ConstraintMatchUri(pConstraint, pName); + + if (RTCRX509GENERALNAME_IS_IP_ADDRESS(pConstraint)) + return rtCrX509GeneralName_ConstraintMatchIpAddress(pConstraint, pName); + + AssertFailed(); + return RTCrX509GeneralName_Compare(pConstraint, pName) == 0; + } + return false; +} + + +/* + * Sequence of X.509 GeneralNames. + */ + + +/* + * X.509 UniqueIdentifier. + */ + + +/* + * X.509 SubjectPublicKeyInfo. + */ + + +/* + * X.509 AuthorityKeyIdentifier (IPRT representation). + */ + + +/* + * One X.509 PolicyQualifierInfo. + */ + + +/* + * Sequence of X.509 PolicyQualifierInfo. + */ + + +/* + * One X.509 PolicyInformation. + */ + + +/* + * Sequence of X.509 CertificatePolicies. + */ + + +/* + * One X.509 PolicyMapping (IPRT representation). + */ + + +/* + * Sequence of X.509 PolicyMappings (IPRT representation). + */ + + +/* + * X.509 BasicConstraints (IPRT representation). + */ + + +/* + * X.509 GeneralSubtree (IPRT representation). + */ + + +RTDECL(bool) RTCrX509GeneralSubtree_ConstraintMatch(PCRTCRX509GENERALSUBTREE pConstraint, PCRTCRX509GENERALSUBTREE pName) +{ + return RTCrX509GeneralName_ConstraintMatch(&pConstraint->Base, &pName->Base); +} + + +/* + * Sequence of X.509 GeneralSubtrees (IPRT representation). + */ + + +/* + * X.509 NameConstraints (IPRT representation). + */ + + +/* + * X.509 PolicyConstraints (IPRT representation). + */ + + +/* + * One X.509 Extension. + */ + + +/* + * Sequence of X.509 Extensions. + */ + + +/* + * X.509 TbsCertificate. + */ + +static void rtCrx509TbsCertificate_AddKeyUsageFlags(PRTCRX509TBSCERTIFICATE pThis, PCRTCRX509EXTENSION pExtension) +{ + AssertReturnVoid(pExtension->enmValue == RTCRX509EXTENSIONVALUE_BIT_STRING); + /* 3 = 1 byte for unused bit count, followed by one or two bytes containing actual bits. RFC-5280 defines bits 0 thru 8. */ + AssertReturnVoid(pExtension->ExtnValue.pEncapsulated->cb <= 3); + pThis->T3.fKeyUsage |= (uint32_t)RTAsn1BitString_GetAsUInt64((PCRTASN1BITSTRING)pExtension->ExtnValue.pEncapsulated); +} + + +static void rtCrx509TbsCertificate_AddExtKeyUsageFlags(PRTCRX509TBSCERTIFICATE pThis, PCRTCRX509EXTENSION pExtension) +{ + AssertReturnVoid(pExtension->enmValue == RTCRX509EXTENSIONVALUE_SEQ_OF_OBJ_IDS); + PCRTASN1SEQOFOBJIDS pObjIds = (PCRTASN1SEQOFOBJIDS)pExtension->ExtnValue.pEncapsulated; + uint32_t i = pObjIds->cItems; + while (i-- > 0) + { + + if (RTAsn1ObjId_CompareWithString(pObjIds->papItems[i], RTCRX509_ANY_EXTENDED_KEY_USAGE_OID) == 0) + pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_ANY; + else if (RTAsn1ObjId_StartsWith(pObjIds->papItems[i], RTCRX509_ID_KP_OID)) + { + if (RTAsn1ObjIdCountComponents(pObjIds->papItems[i]) == 9) + switch (RTAsn1ObjIdGetLastComponentsAsUInt32(pObjIds->papItems[i])) + { + case 1: pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_SERVER_AUTH; break; + case 2: pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_CLIENT_AUTH; break; + case 3: pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_CODE_SIGNING; break; + case 4: pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_EMAIL_PROTECTION; break; + case 5: pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_IPSEC_END_SYSTEM; break; + case 6: pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_IPSEC_TUNNEL; break; + case 7: pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_IPSEC_USER; break; + case 8: pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_TIMESTAMPING; break; + case 9: pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_OCSP_SIGNING; break; + case 10: pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_DVCS; break; + case 11: pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_SBGP_CERT_AA_SERVICE_AUTH; break; + case 13: pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_EAP_OVER_PPP; break; + case 14: pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_EAP_OVER_LAN; break; + default: pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_OTHER; break; + } + else + pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_OTHER; + } + else if (RTAsn1ObjId_StartsWith(pObjIds->papItems[i], RTCRX509_APPLE_EKU_APPLE_EXTENDED_KEY_USAGE_OID)) + { + if (RTAsn1ObjId_CompareWithString(pObjIds->papItems[i], RTCRX509_APPLE_EKU_CODE_SIGNING_OID) == 0) + pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_APPLE_CODE_SIGNING; + else if (RTAsn1ObjId_CompareWithString(pObjIds->papItems[i], RTCRX509_APPLE_EKU_CODE_SIGNING_DEVELOPMENT_OID) == 0) + pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_APPLE_CODE_SIGNING_DEVELOPMENT; + else if (RTAsn1ObjId_CompareWithString(pObjIds->papItems[i], RTCRX509_APPLE_EKU_SOFTWARE_UPDATE_SIGNING_OID) == 0) + pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_APPLE_SOFTWARE_UPDATE_SIGNING; + else if (RTAsn1ObjId_CompareWithString(pObjIds->papItems[i], RTCRX509_APPLE_EKU_CODE_SIGNING_THRID_PARTY_OID) == 0) + pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_APPLE_CODE_SIGNING_THIRD_PARTY; + else if (RTAsn1ObjId_CompareWithString(pObjIds->papItems[i], RTCRX509_APPLE_EKU_RESOURCE_SIGNING_OID) == 0) + pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_APPLE_RESOURCE_SIGNING; + else if (RTAsn1ObjId_CompareWithString(pObjIds->papItems[i], RTCRX509_APPLE_EKU_SYSTEM_IDENTITY_OID) == 0) + pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_APPLE_SYSTEM_IDENTITY; + else + pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_OTHER; + } + else if (RTAsn1ObjId_StartsWith(pObjIds->papItems[i], "1.3.6.1.4.1.311")) + { + if (RTAsn1ObjId_CompareWithString(pObjIds->papItems[i], RTCRX509_MS_EKU_TIMESTAMP_SIGNING_OID) == 0) + pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_MS_TIMESTAMP_SIGNING; + else if (RTAsn1ObjId_CompareWithString(pObjIds->papItems[i], RTCRX509_MS_EKU_WHQL_CRYPTO_OID) == 0) + pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_MS_WHQL_CRYPTO; + else if (RTAsn1ObjId_CompareWithString(pObjIds->papItems[i], RTCRX509_MS_EKU_ATTEST_WHQL_CRYPTO_OID) == 0) + pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_MS_ATTEST_WHQL_CRYPTO; + else if (RTAsn1ObjId_CompareWithString(pObjIds->papItems[i], RTCRX509_MS_EKU_NT5_CRYPTO_OID) == 0) + pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_MS_NT5_CRYPTO; + else if (RTAsn1ObjId_CompareWithString(pObjIds->papItems[i], RTCRX509_MS_EKU_OEM_WHQL_CRYPTO_OID) == 0) + pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_MS_OEM_WHQL_CRYPTO; + else if (RTAsn1ObjId_CompareWithString(pObjIds->papItems[i], RTCRX509_MS_EKU_EMBEDDED_NT_CRYPTO_OID) == 0) + pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_MS_EMBEDDED_NT_CRYPTO; + else if (RTAsn1ObjId_CompareWithString(pObjIds->papItems[i], RTCRX509_MS_EKU_KERNEL_MODE_CODE_SIGNING_OID) == 0) + pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_MS_KERNEL_MODE_CODE_SIGNING; + else if (RTAsn1ObjId_CompareWithString(pObjIds->papItems[i], RTCRX509_MS_EKU_LIFETIME_SIGNING_OID) == 0) + pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_MS_LIFETIME_SIGNING; + else if (RTAsn1ObjId_CompareWithString(pObjIds->papItems[i], RTCRX509_MS_EKU_DRM_OID) == 0) + pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_MS_DRM; + else if (RTAsn1ObjId_CompareWithString(pObjIds->papItems[i], RTCRX509_MS_EKU_DRM_INDIVIDUALIZATION_OID) == 0) + pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_MS_DRM_INDIVIDUALIZATION; + else + pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_OTHER; + } + else + pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_OTHER; + } +} + + +/** + * (Re-)Process the certificate extensions. + * + * Will fail if duplicate extensions are encountered. + * + * @returns IPRT status code. + * @param pThis The to-be-signed certificate part. + * @param pErrInfo Where to return extended error details, + * optional. + */ +RTDECL(int) RTCrX509TbsCertificate_ReprocessExtensions(PRTCRX509TBSCERTIFICATE pThis, PRTERRINFO pErrInfo) +{ + /* + * Clear all variables we will set. + */ + pThis->T3.fFlags = 0; + pThis->T3.fKeyUsage = 0; + pThis->T3.fExtKeyUsage = 0; + pThis->T3.pAuthorityKeyIdentifier = NULL; + pThis->T3.pSubjectKeyIdentifier = NULL; + pThis->T3.pAltSubjectName = NULL; + pThis->T3.pAltIssuerName = NULL; + pThis->T3.pCertificatePolicies = NULL; + pThis->T3.pPolicyMappings = NULL; + pThis->T3.pBasicConstraints = NULL; + pThis->T3.pNameConstraints = NULL; + pThis->T3.pPolicyConstraints = NULL; + pThis->T3.pInhibitAnyPolicy = NULL; + +#define CHECK_SET_PRESENT_RET_ON_DUP(a_pThis, a_pErrInfo, a_fPresentFlag) \ + do { \ + if ((a_pThis)->T3.fFlags & (a_fPresentFlag)) \ + return RTErrInfoSet(a_pErrInfo, VERR_CR_X509_TBSCERT_DUPLICATE_EXTENSION, \ + "Duplicate extension " #a_fPresentFlag); \ + (a_pThis)->T3.fFlags |= (a_fPresentFlag); \ + } while (0) + + /* + * Process all the extensions. + */ + for (uint32_t i = 0; i < pThis->T3.Extensions.cItems; i++) + { + PCRTASN1OBJID pExtnId = &pThis->T3.Extensions.papItems[i]->ExtnId; + PCRTASN1OCTETSTRING pExtValue = &pThis->T3.Extensions.papItems[i]->ExtnValue; + if (RTAsn1ObjId_CompareWithString(pExtnId, RTCRX509_ID_CE_KEY_USAGE_OID) == 0) + { + CHECK_SET_PRESENT_RET_ON_DUP(pThis, pErrInfo, RTCRX509TBSCERTIFICATE_F_PRESENT_KEY_USAGE); + rtCrx509TbsCertificate_AddKeyUsageFlags(pThis, pThis->T3.Extensions.papItems[i]); + Assert(pThis->T3.Extensions.papItems[i]->enmValue == RTCRX509EXTENSIONVALUE_BIT_STRING); + } + else if (RTAsn1ObjId_CompareWithString(pExtnId, RTCRX509_ID_CE_EXT_KEY_USAGE_OID) == 0) + { + CHECK_SET_PRESENT_RET_ON_DUP(pThis, pErrInfo, RTCRX509TBSCERTIFICATE_F_PRESENT_EXT_KEY_USAGE); + rtCrx509TbsCertificate_AddExtKeyUsageFlags(pThis, pThis->T3.Extensions.papItems[i]); + Assert(pThis->T3.Extensions.papItems[i]->enmValue == RTCRX509EXTENSIONVALUE_SEQ_OF_OBJ_IDS); + } + else if (RTAsn1ObjId_CompareWithString(pExtnId, RTCRX509_ID_CE_AUTHORITY_KEY_IDENTIFIER_OID) == 0) + { + CHECK_SET_PRESENT_RET_ON_DUP(pThis, pErrInfo, RTCRX509TBSCERTIFICATE_F_PRESENT_AUTHORITY_KEY_IDENTIFIER); + pThis->T3.pAuthorityKeyIdentifier = (PCRTCRX509AUTHORITYKEYIDENTIFIER)pExtValue->pEncapsulated; + Assert(pThis->T3.Extensions.papItems[i]->enmValue == RTCRX509EXTENSIONVALUE_AUTHORITY_KEY_IDENTIFIER); + } + else if (RTAsn1ObjId_CompareWithString(pExtnId, RTCRX509_ID_CE_OLD_AUTHORITY_KEY_IDENTIFIER_OID) == 0) + { + CHECK_SET_PRESENT_RET_ON_DUP(pThis, pErrInfo, RTCRX509TBSCERTIFICATE_F_PRESENT_OLD_AUTHORITY_KEY_IDENTIFIER); + pThis->T3.pOldAuthorityKeyIdentifier = (PCRTCRX509OLDAUTHORITYKEYIDENTIFIER)pExtValue->pEncapsulated; + Assert(pThis->T3.Extensions.papItems[i]->enmValue == RTCRX509EXTENSIONVALUE_OLD_AUTHORITY_KEY_IDENTIFIER); + } + else if (RTAsn1ObjId_CompareWithString(pExtnId, RTCRX509_ID_CE_SUBJECT_KEY_IDENTIFIER_OID) == 0) + { + CHECK_SET_PRESENT_RET_ON_DUP(pThis, pErrInfo, RTCRX509TBSCERTIFICATE_F_PRESENT_SUBJECT_KEY_IDENTIFIER); + pThis->T3.pSubjectKeyIdentifier = (PCRTASN1OCTETSTRING)pExtValue->pEncapsulated; + Assert(pThis->T3.Extensions.papItems[i]->enmValue == RTCRX509EXTENSIONVALUE_OCTET_STRING); + } + else if (RTAsn1ObjId_CompareWithString(pExtnId, RTCRX509_ID_CE_SUBJECT_ALT_NAME_OID) == 0) + { + CHECK_SET_PRESENT_RET_ON_DUP(pThis, pErrInfo, RTCRX509TBSCERTIFICATE_F_PRESENT_SUBJECT_ALT_NAME); + pThis->T3.pAltSubjectName = (PCRTCRX509GENERALNAMES)pExtValue->pEncapsulated; + Assert(pThis->T3.Extensions.papItems[i]->enmValue == RTCRX509EXTENSIONVALUE_GENERAL_NAMES); + } + else if (RTAsn1ObjId_CompareWithString(pExtnId, RTCRX509_ID_CE_ISSUER_ALT_NAME_OID) == 0) + { + CHECK_SET_PRESENT_RET_ON_DUP(pThis, pErrInfo, RTCRX509TBSCERTIFICATE_F_PRESENT_ISSUER_ALT_NAME); + pThis->T3.pAltIssuerName = (PCRTCRX509GENERALNAMES)pExtValue->pEncapsulated; + Assert(pThis->T3.Extensions.papItems[i]->enmValue == RTCRX509EXTENSIONVALUE_GENERAL_NAMES); + } + else if (RTAsn1ObjId_CompareWithString(pExtnId, RTCRX509_ID_CE_CERTIFICATE_POLICIES_OID) == 0) + { + CHECK_SET_PRESENT_RET_ON_DUP(pThis, pErrInfo, RTCRX509TBSCERTIFICATE_F_PRESENT_CERTIFICATE_POLICIES); + pThis->T3.pCertificatePolicies = (PCRTCRX509CERTIFICATEPOLICIES)pExtValue->pEncapsulated; + Assert(pThis->T3.Extensions.papItems[i]->enmValue == RTCRX509EXTENSIONVALUE_CERTIFICATE_POLICIES); + } + else if (RTAsn1ObjId_CompareWithString(pExtnId, RTCRX509_ID_CE_POLICY_MAPPINGS_OID) == 0) + { + CHECK_SET_PRESENT_RET_ON_DUP(pThis, pErrInfo, RTCRX509TBSCERTIFICATE_F_PRESENT_POLICY_MAPPINGS); + pThis->T3.pPolicyMappings = (PCRTCRX509POLICYMAPPINGS)pExtValue->pEncapsulated; + Assert(pThis->T3.Extensions.papItems[i]->enmValue == RTCRX509EXTENSIONVALUE_POLICY_MAPPINGS); + } + else if (RTAsn1ObjId_CompareWithString(pExtnId, RTCRX509_ID_CE_BASIC_CONSTRAINTS_OID) == 0) + { + CHECK_SET_PRESENT_RET_ON_DUP(pThis, pErrInfo, RTCRX509TBSCERTIFICATE_F_PRESENT_BASIC_CONSTRAINTS); + pThis->T3.pBasicConstraints = (PCRTCRX509BASICCONSTRAINTS)pExtValue->pEncapsulated; + Assert(pThis->T3.Extensions.papItems[i]->enmValue == RTCRX509EXTENSIONVALUE_BASIC_CONSTRAINTS); + } + else if (RTAsn1ObjId_CompareWithString(pExtnId, RTCRX509_ID_CE_NAME_CONSTRAINTS_OID) == 0) + { + CHECK_SET_PRESENT_RET_ON_DUP(pThis, pErrInfo, RTCRX509TBSCERTIFICATE_F_PRESENT_NAME_CONSTRAINTS); + pThis->T3.pNameConstraints = (PCRTCRX509NAMECONSTRAINTS)pExtValue->pEncapsulated; + Assert(pThis->T3.Extensions.papItems[i]->enmValue == RTCRX509EXTENSIONVALUE_NAME_CONSTRAINTS); + } + else if (RTAsn1ObjId_CompareWithString(pExtnId, RTCRX509_ID_CE_POLICY_CONSTRAINTS_OID) == 0) + { + CHECK_SET_PRESENT_RET_ON_DUP(pThis, pErrInfo, RTCRX509TBSCERTIFICATE_F_PRESENT_POLICY_CONSTRAINTS); + pThis->T3.pPolicyConstraints = (PCRTCRX509POLICYCONSTRAINTS)pExtValue->pEncapsulated; + Assert(pThis->T3.Extensions.papItems[i]->enmValue == RTCRX509EXTENSIONVALUE_POLICY_CONSTRAINTS); + } + else if (RTAsn1ObjId_CompareWithString(pExtnId, RTCRX509_ID_CE_INHIBIT_ANY_POLICY_OID) == 0) + { + CHECK_SET_PRESENT_RET_ON_DUP(pThis, pErrInfo, RTCRX509TBSCERTIFICATE_F_PRESENT_INHIBIT_ANY_POLICY); + pThis->T3.pInhibitAnyPolicy = (PCRTASN1INTEGER)pExtValue->pEncapsulated; + Assert(pThis->T3.Extensions.papItems[i]->enmValue == RTCRX509EXTENSIONVALUE_INTEGER); + } + else if (RTAsn1ObjId_CompareWithString(pExtnId, RTCRX509_ID_CE_ACCEPTABLE_CERT_POLICIES_OID) == 0) + pThis->T3.fFlags |= RTCRX509TBSCERTIFICATE_F_PRESENT_ACCEPTABLE_CERT_POLICIES; + else + pThis->T3.fFlags |= RTCRX509TBSCERTIFICATE_F_PRESENT_OTHER; + } + + if (!pThis->T3.fFlags) + pThis->T3.fFlags |= RTCRX509TBSCERTIFICATE_F_PRESENT_NONE; + +#undef CHECK_SET_PRESENT_RET_ON_DUP + return VINF_SUCCESS; +} + + + +/* + * One X.509 Certificate. + */ + +RTDECL(bool) RTCrX509Certificate_MatchIssuerAndSerialNumber(PCRTCRX509CERTIFICATE pCertificate, + PCRTCRX509NAME pIssuer, PCRTASN1INTEGER pSerialNumber) +{ + if ( RTAsn1Integer_UnsignedCompare(&pCertificate->TbsCertificate.SerialNumber, pSerialNumber) == 0 + && RTCrX509Name_Compare(&pCertificate->TbsCertificate.Issuer, pIssuer) == 0) + return true; + return false; +} + + +RTDECL(bool) RTCrX509Certificate_MatchSubjectOrAltSubjectByRfc5280(PCRTCRX509CERTIFICATE pThis, PCRTCRX509NAME pName) +{ + if (RTCrX509Name_MatchByRfc5280(&pThis->TbsCertificate.Subject, pName)) + return true; + + if (RTCrX509Extensions_IsPresent(&pThis->TbsCertificate.T3.Extensions)) + for (uint32_t i = 0; i < pThis->TbsCertificate.T3.Extensions.cItems; i++) + { + PCRTCRX509EXTENSION pExt = pThis->TbsCertificate.T3.Extensions.papItems[i]; + if ( pExt->enmValue == RTCRX509EXTENSIONVALUE_GENERAL_NAMES + && RTAsn1ObjId_CompareWithString(&pExt->ExtnId, RTCRX509_ID_CE_SUBJECT_ALT_NAME_OID)) + { + PCRTCRX509GENERALNAMES pGeneralNames = (PCRTCRX509GENERALNAMES)pExt->ExtnValue.pEncapsulated; + for (uint32_t j = 0; j < pGeneralNames->cItems; j++) + if ( RTCRX509GENERALNAME_IS_DIRECTORY_NAME(pGeneralNames->papItems[j]) + && RTCrX509Name_MatchByRfc5280(&pGeneralNames->papItems[j]->u.pT4->DirectoryName, pName)) + return true; + } + } + return false; +} + + +RTDECL(bool) RTCrX509Certificate_IsSelfSigned(PCRTCRX509CERTIFICATE pCertificate) +{ + if (RTCrX509Certificate_IsPresent(pCertificate)) + { + return RTCrX509Name_MatchByRfc5280(&pCertificate->TbsCertificate.Subject, + &pCertificate->TbsCertificate.Issuer); + } + return false; +} + + +/* + * Set of X.509 Certificates. + */ + +RTDECL(PCRTCRX509CERTIFICATE) +RTCrX509Certificates_FindByIssuerAndSerialNumber(PCRTCRX509CERTIFICATES pCertificates, + PCRTCRX509NAME pIssuer, PCRTASN1INTEGER pSerialNumber) +{ + for (uint32_t i = 0; i < pCertificates->cItems; i++) + if (RTCrX509Certificate_MatchIssuerAndSerialNumber(pCertificates->papItems[i], pIssuer, pSerialNumber)) + return pCertificates->papItems[i]; + return NULL; +} + diff --git a/src/VBox/Runtime/common/crypto/x509-file.cpp b/src/VBox/Runtime/common/crypto/x509-file.cpp new file mode 100644 index 00000000..ecd33c85 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/x509-file.cpp @@ -0,0 +1,174 @@ +/* $Id: x509-file.cpp $ */ +/** @file + * IPRT - Crypto - X.509, File related APIs. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/x509.h> + +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/path.h> +#include <iprt/crypto/pem.h> + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static RTCRPEMMARKERWORD const g_aWords_Certificate[] = { { RT_STR_TUPLE("CERTIFICATE") } }; +/** X509 Certificate markers. */ +RT_DECL_DATA_CONST(RTCRPEMMARKER const) g_aRTCrX509CertificateMarkers[] = +{ + { g_aWords_Certificate, RT_ELEMENTS(g_aWords_Certificate) } +}; +/** Number of entries in g_aRTCrX509CertificateMarkers. */ +RT_DECL_DATA_CONST(uint32_t const) g_cRTCrX509CertificateMarkers = RT_ELEMENTS(g_aRTCrX509CertificateMarkers); + + +RTDECL(int) RTCrX509Certificate_ReadFromFile(PRTCRX509CERTIFICATE pCertificate, const char *pszFilename, uint32_t fFlags, + PCRTASN1ALLOCATORVTABLE pAllocator, PRTERRINFO pErrInfo) +{ + AssertReturn(!(fFlags & ~RTCRX509CERT_READ_F_PEM_ONLY), VERR_INVALID_FLAGS); + PCRTCRPEMSECTION pSectionHead; + int rc = RTCrPemReadFile(pszFilename, + fFlags & RTCRX509CERT_READ_F_PEM_ONLY ? RTCRPEMREADFILE_F_ONLY_PEM : 0, + g_aRTCrX509CertificateMarkers, g_cRTCrX509CertificateMarkers, + &pSectionHead, pErrInfo); + if (RT_SUCCESS(rc)) + { + if (pSectionHead) + { + RTCRX509CERTIFICATE TmpCert; + RTASN1CURSORPRIMARY PrimaryCursor; + RTAsn1CursorInitPrimary(&PrimaryCursor, pSectionHead->pbData, (uint32_t)RT_MIN(pSectionHead->cbData, UINT32_MAX), + pErrInfo, pAllocator, RTASN1CURSOR_FLAGS_DER, RTPathFilename(pszFilename)); + rc = RTCrX509Certificate_DecodeAsn1(&PrimaryCursor.Cursor, 0, &TmpCert, "Cert"); + if (RT_SUCCESS(rc)) + { + rc = RTCrX509Certificate_CheckSanity(&TmpCert, 0, pErrInfo, "Cert"); + if (RT_SUCCESS(rc)) + { + rc = RTCrX509Certificate_Clone(pCertificate, &TmpCert, pAllocator); + if (RT_SUCCESS(rc)) + { + if (pSectionHead->pNext || PrimaryCursor.Cursor.cbLeft) + rc = VINF_ASN1_MORE_DATA; + } + } + RTCrX509Certificate_Delete(&TmpCert); + } + RTCrPemFreeSections(pSectionHead); + } + else + rc = rc != VINF_SUCCESS ? -rc : VERR_INTERNAL_ERROR_2; + + } + return rc; +} + + +RTDECL(int) RTCrX509Certificate_ReadFromBuffer(PRTCRX509CERTIFICATE pCertificate, const void *pvBuf, size_t cbBuf, + uint32_t fFlags, PCRTASN1ALLOCATORVTABLE pAllocator, + PRTERRINFO pErrInfo, const char *pszErrorTag) +{ + AssertReturn(!(fFlags & ~RTCRX509CERT_READ_F_PEM_ONLY), VERR_INVALID_FLAGS); + PCRTCRPEMSECTION pSectionHead; + int rc = RTCrPemParseContent(pvBuf, cbBuf, + fFlags & RTCRX509CERT_READ_F_PEM_ONLY ? RTCRPEMREADFILE_F_ONLY_PEM : 0, + g_aRTCrX509CertificateMarkers, g_cRTCrX509CertificateMarkers, + &pSectionHead, pErrInfo); + if (RT_SUCCESS(rc)) + { + if (pSectionHead) + { + RTCRX509CERTIFICATE TmpCert; + RTASN1CURSORPRIMARY PrimaryCursor; + RTAsn1CursorInitPrimary(&PrimaryCursor, pSectionHead->pbData, (uint32_t)RT_MIN(pSectionHead->cbData, UINT32_MAX), + pErrInfo, pAllocator, RTASN1CURSOR_FLAGS_DER, pszErrorTag); + rc = RTCrX509Certificate_DecodeAsn1(&PrimaryCursor.Cursor, 0, &TmpCert, "Cert"); + if (RT_SUCCESS(rc)) + { + rc = RTCrX509Certificate_CheckSanity(&TmpCert, 0, pErrInfo, "Cert"); + if (RT_SUCCESS(rc)) + { + rc = RTCrX509Certificate_Clone(pCertificate, &TmpCert, pAllocator); + if (RT_SUCCESS(rc)) + { + if (pSectionHead->pNext || PrimaryCursor.Cursor.cbLeft) + rc = VINF_ASN1_MORE_DATA; + } + } + RTCrX509Certificate_Delete(&TmpCert); + } + RTCrPemFreeSections(pSectionHead); + } + else + rc = rc != VINF_SUCCESS ? -rc : VERR_INTERNAL_ERROR_2; + } + return rc; +} + + + +#if 0 +RTDECL(int) RTCrX509Certificates_ReadFromFile(const char *pszFilename, uint32_t fFlags, + PRTCRX509CERTIFICATES pCertificates, PRTERRINFO pErrInfo) +{ + AssertReturn(!(fFlags & ~RTCRX509CERT_READ_F_PEM_ONLY), VERR_INVALID_FLAGS); + PCRTCRPEMSECTION pSectionHead; + int rc = RTCrPemReadFile(pszFilename, + fFlags & RTCRX509CERT_READ_F_PEM_ONLY ? RTCRPEMREADFILE_F_ONLY_PEM : 0, + g_aRTCrX509CertificateMarkers, g_cRTCrX509CertificateMarkers, + &pSectionHead, pErrInfo); + if (RT_SUCCESS(rc)) + { + pCertificates->Allocation + + PCRTCRPEMSECTION pCurSec = pSectionHead; + while (pCurSec) + { + + pCurSec = pCurSec->pNext; + } + + RTCrPemFreeSections(pSectionHead); + } + return rc; +} +#endif + diff --git a/src/VBox/Runtime/common/crypto/x509-init.cpp b/src/VBox/Runtime/common/crypto/x509-init.cpp new file mode 100644 index 00000000..cb77a4a3 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/x509-init.cpp @@ -0,0 +1,88 @@ +/* $Id: x509-init.cpp $ */ +/** @file + * IPRT - Crypto - X.509, Initialization API. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/x509.h> + +#include <iprt/errcore.h> +#include <iprt/string.h> +#include <iprt/uni.h> + +#include "x509-internal.h" + + +static int rtCrX509Extension_ExtnValue_Clone(PRTCRX509EXTENSION pThis, PCRTCRX509EXTENSION pSrc) +{ + pThis->enmValue = pSrc->enmValue; + return VINF_SUCCESS; +} + + +RTDECL(int) RTCrX509Name_RecodeAsUtf8(PRTCRX509NAME pThis, PCRTASN1ALLOCATORVTABLE pAllocator) +{ + uint32_t cRdns = pThis->cItems; + PRTCRX509RELATIVEDISTINGUISHEDNAME *ppRdn = pThis->papItems; + while (cRdns-- > 0) + { + PRTCRX509RELATIVEDISTINGUISHEDNAME const pRdn = *ppRdn; + uint32_t cAttribs = pRdn->cItems; + PRTCRX509ATTRIBUTETYPEANDVALUE *ppAttrib = pRdn->papItems; + while (cAttribs-- > 0) + { + PRTCRX509ATTRIBUTETYPEANDVALUE const pAttrib = *ppAttrib; + if (pAttrib->Value.enmType == RTASN1TYPE_STRING) + { + int rc = RTAsn1String_RecodeAsUtf8(&pAttrib->Value.u.String, pAllocator); + if (RT_FAILURE(rc)) + return rc; + } + ppAttrib++; + } + ppRdn++; + } + return VINF_SUCCESS; +} + + +/* + * Generate the code. + */ +#include <iprt/asn1-generator-init.h> + diff --git a/src/VBox/Runtime/common/crypto/x509-internal.h b/src/VBox/Runtime/common/crypto/x509-internal.h new file mode 100644 index 00000000..e2a34284 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/x509-internal.h @@ -0,0 +1,47 @@ +/* $Id: x509-internal.h $ */ +/** @file + * IPRT - Crypto - X.509, Internal Header. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef IPRT_INCLUDED_SRC_common_crypto_x509_internal_h +#define IPRT_INCLUDED_SRC_common_crypto_x509_internal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#define RTASN1TMPL_TEMPLATE_FILE "../common/crypto/x509-template.h" +#include <iprt/asn1-generator-internal-header.h> + +#endif /* !IPRT_INCLUDED_SRC_common_crypto_x509_internal_h */ + diff --git a/src/VBox/Runtime/common/crypto/x509-sanity.cpp b/src/VBox/Runtime/common/crypto/x509-sanity.cpp new file mode 100644 index 00000000..000b4326 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/x509-sanity.cpp @@ -0,0 +1,171 @@ +/* $Id: x509-sanity.cpp $ */ +/** @file + * IPRT - Crypto - X.509, Sanity Checkers. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/x509.h> + +#include <iprt/err.h> +#include <iprt/string.h> + +#include "x509-internal.h" + + + +static int rtCrX509Validity_CheckSanityExtra(PCRTCRX509VALIDITY pThis, uint32_t fFlags, PRTERRINFO pErrInfo, const char *pszErrorTag) +{ + RT_NOREF_PV(fFlags); + + if (RTAsn1Time_Compare(&pThis->NotBefore, &pThis->NotAfter) > 0) + return RTErrInfoSetF(pErrInfo, VERR_CR_X509_VALIDITY_SWAPPED, "%s: NotBefore is after NotAfter", pszErrorTag); + /** @todo check tag constraints? */ + return VINF_SUCCESS; +} + + +static int rtCrX509Name_CheckSanityExtra(PCRTCRX509NAME pThis, uint32_t fFlags, PRTERRINFO pErrInfo, const char *pszErrorTag) +{ + RT_NOREF_PV(fFlags); + + if (pThis->cItems == 0) + return RTErrInfoSetF(pErrInfo, VERR_CR_X509_NAME_EMPTY_SET, "%s: Has no components.", pszErrorTag); + + for (uint32_t i = 0; i < pThis->cItems; i++) + { + PCRTCRX509RELATIVEDISTINGUISHEDNAME const pRdn = pThis->papItems[i]; + if (pRdn->cItems == 0) + return RTErrInfoSetF(pErrInfo, VERR_CR_X509_NAME_EMPTY_SUB_SET, + "%s: Items[%u] has no sub components.", pszErrorTag, i); + + for (uint32_t j = 0; j < pRdn->cItems; j++) + { + PCRTCRX509ATTRIBUTETYPEANDVALUE const pAttr = pRdn->papItems[j]; + + if (pAttr->Value.enmType != RTASN1TYPE_STRING) + return RTErrInfoSetF(pErrInfo, VERR_CR_X509_NAME_NOT_STRING, + "%s: Items[%u].paItems[%u].enmType is %d instead of string (%d).", + pszErrorTag, i, j, pAttr->Value.enmType, RTASN1TYPE_STRING); + if (pAttr->Value.u.String.Asn1Core.cb == 0) + return RTErrInfoSetF(pErrInfo, VERR_CR_X509_NAME_EMPTY_STRING, + "%s: Items[%u].paItems[%u] is an empty string", pszErrorTag, i, j); + switch (pAttr->Value.u.String.Asn1Core.uTag) + { + case ASN1_TAG_PRINTABLE_STRING: + case ASN1_TAG_UTF8_STRING: + break; + case ASN1_TAG_T61_STRING: + case ASN1_TAG_UNIVERSAL_STRING: + case ASN1_TAG_BMP_STRING: + break; + case ASN1_TAG_IA5_STRING: /* Used by "Microsoft Root Certificate Authority" in the "com" part of the Issuer. */ + break; + default: + return RTErrInfoSetF(pErrInfo, VERR_CR_X509_INVALID_NAME_STRING_TAG, + "%s: Items[%u].paItems[%u] invalid string type: %u", pszErrorTag, i, j, + pAttr->Value.u.String.Asn1Core.uTag); + } + } + } + + return VINF_SUCCESS; +} + + +static int rtCrX509SubjectPublicKeyInfo_CheckSanityExtra(PCRTCRX509SUBJECTPUBLICKEYINFO pThis, uint32_t fFlags, + PRTERRINFO pErrInfo, const char *pszErrorTag) +{ + RT_NOREF_PV(fFlags); + if (pThis->SubjectPublicKey.cBits <= 32) + return RTErrInfoSetF(pErrInfo, VERR_CR_X509_PUBLIC_KEY_TOO_SMALL, + "%s: SubjectPublicKey is too small, only %u bits", pszErrorTag, pThis->SubjectPublicKey.cBits); + return VINF_SUCCESS; +} + + +static int rtCrX509TbsCertificate_CheckSanityExtra(PCRTCRX509TBSCERTIFICATE pThis, uint32_t fFlags, + PRTERRINFO pErrInfo, const char *pszErrorTag) +{ + RT_NOREF_PV(fFlags); + + if ( RTAsn1Integer_IsPresent(&pThis->T0.Version) + && RTAsn1Integer_UnsignedCompareWithU32(&pThis->T0.Version, RTCRX509TBSCERTIFICATE_V1) != 0 + && RTAsn1Integer_UnsignedCompareWithU32(&pThis->T0.Version, RTCRX509TBSCERTIFICATE_V2) != 0 + && RTAsn1Integer_UnsignedCompareWithU32(&pThis->T0.Version, RTCRX509TBSCERTIFICATE_V3) != 0) + return RTErrInfoSetF(pErrInfo, VERR_CR_X509_TBSCERT_UNSUPPORTED_VERSION, + "%s: Unknown Version number: %llu", + pszErrorTag, pThis->T0.Version.uValue.u); + + if ( pThis->SerialNumber.Asn1Core.cb < 1 + || pThis->SerialNumber.Asn1Core.cb > 1024) + return RTErrInfoSetF(pErrInfo, VERR_CR_X509_TBSCERT_SERIAL_NUMBER_OUT_OF_BOUNDS, + "%s: Bad SerialNumber length: %u", pszErrorTag, pThis->SerialNumber.Asn1Core.cb); + + if ( ( RTAsn1BitString_IsPresent(&pThis->T1.IssuerUniqueId) + || RTAsn1BitString_IsPresent(&pThis->T2.SubjectUniqueId)) + && RTAsn1Integer_UnsignedCompareWithU32(&pThis->T0.Version, RTCRX509TBSCERTIFICATE_V2) < 0) + return RTErrInfoSetF(pErrInfo, VERR_CR_X509_TBSCERT_UNIQUE_IDS_REQ_V2, + "%s: IssuerUniqueId and SubjectUniqueId requires version 2", pszErrorTag); + + if ( RTCrX509Extensions_IsPresent(&pThis->T3.Extensions) + && RTAsn1Integer_UnsignedCompareWithU32(&pThis->T0.Version, RTCRX509TBSCERTIFICATE_V3) < 0) + return RTErrInfoSetF(pErrInfo, VERR_CR_X509_TBSCERT_EXTS_REQ_V3, "%s: Extensions requires version 3", pszErrorTag); + + return VINF_SUCCESS; +} + + +static int rtCrX509Certificate_CheckSanityExtra(PCRTCRX509CERTIFICATE pThis, uint32_t fFlags, + PRTERRINFO pErrInfo, const char *pszErrorTag) +{ + RT_NOREF_PV(fFlags); + + if (RTCrX509AlgorithmIdentifier_Compare(&pThis->SignatureAlgorithm, &pThis->TbsCertificate.Signature) != 0) + return RTErrInfoSetF(pErrInfo, VERR_CR_X509_CERT_TBS_SIGN_ALGO_MISMATCH, + "%s: SignatureAlgorithm (%s) does not match TbsCertificate.Signature (%s).", pszErrorTag, + pThis->SignatureAlgorithm.Algorithm.szObjId, + pThis->TbsCertificate.Signature.Algorithm.szObjId); + return VINF_SUCCESS; +} + + +/* + * Generate the code. + */ +#include <iprt/asn1-generator-sanity.h> + diff --git a/src/VBox/Runtime/common/crypto/x509-template.h b/src/VBox/Runtime/common/crypto/x509-template.h new file mode 100644 index 00000000..07a16c75 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/x509-template.h @@ -0,0 +1,468 @@ +/* $Id: x509-template.h $ */ +/** @file + * IPRT - Crypto - X.509, Code Generator Template. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#define RTASN1TMPL_DECL RTDECL + +/* + * X.509 Validity. + */ +#define RTASN1TMPL_TYPE RTCRX509VALIDITY +#define RTASN1TMPL_EXT_NAME RTCrX509Validity +#define RTASN1TMPL_INT_NAME rtCrX509Validity +RTASN1TMPL_BEGIN_SEQCORE(); +RTASN1TMPL_MEMBER( NotBefore, RTASN1TIME, RTAsn1Time); +RTASN1TMPL_MEMBER( NotAfter, RTASN1TIME, RTAsn1Time); +RTASN1TMPL_EXEC_CHECK_SANITY( rc = rtCrX509Validity_CheckSanityExtra(pThis, fFlags, pErrInfo, pszErrorTag) ) +RTASN1TMPL_END_SEQCORE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * One X.509 Algorithm Identifier. + */ +#define RTASN1TMPL_TYPE RTCRX509ALGORITHMIDENTIFIER +#define RTASN1TMPL_EXT_NAME RTCrX509AlgorithmIdentifier +#define RTASN1TMPL_INT_NAME rtCrX509AlgorithmIdentifier +RTASN1TMPL_BEGIN_SEQCORE(); +RTASN1TMPL_MEMBER( Algorithm, RTASN1OBJID, RTAsn1ObjId); +RTASN1TMPL_MEMBER_OPT_ANY( Parameters, RTASN1DYNTYPE, RTAsn1DynType); +RTASN1TMPL_END_SEQCORE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * Set of X.509 Algorithm Identifiers. + */ +#define RTASN1TMPL_TYPE RTCRX509ALGORITHMIDENTIFIERS +#define RTASN1TMPL_EXT_NAME RTCrX509AlgorithmIdentifiers +#define RTASN1TMPL_INT_NAME rtCrX509AlgorithmIdentifiers +RTASN1TMPL_SET_OF(RTCRX509ALGORITHMIDENTIFIER, RTCrX509AlgorithmIdentifier); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * One X.509 AttributeTypeAndValue. + */ +#define RTASN1TMPL_TYPE RTCRX509ATTRIBUTETYPEANDVALUE +#define RTASN1TMPL_EXT_NAME RTCrX509AttributeTypeAndValue +#define RTASN1TMPL_INT_NAME rtCrX509AttributeTypeAndValue +RTASN1TMPL_BEGIN_SEQCORE(); +RTASN1TMPL_MEMBER( Type, RTASN1OBJID, RTAsn1ObjId); +RTASN1TMPL_MEMBER( Value, RTASN1DYNTYPE, RTAsn1DynType); +RTASN1TMPL_END_SEQCORE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * Set of X.509 AttributeTypeAndValues / X.509 RelativeDistinguishedName. + */ +#define RTASN1TMPL_TYPE RTCRX509ATTRIBUTETYPEANDVALUES +#define RTASN1TMPL_EXT_NAME RTCrX509AttributeTypeAndValues +#define RTASN1TMPL_INT_NAME rtCrX509AttributeTypeAndValues +RTASN1TMPL_SET_OF(RTCRX509ATTRIBUTETYPEANDVALUE, RTCrX509AttributeTypeAndValue); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + +/* + * X.509 Name. + */ +#define RTASN1TMPL_TYPE RTCRX509NAME +#define RTASN1TMPL_EXT_NAME RTCrX509Name +#define RTASN1TMPL_INT_NAME rtCrX509Name +#undef RTASN1TMPL_SET_SEQ_EXEC_CHECK_SANITY +#define RTASN1TMPL_SET_SEQ_EXEC_CHECK_SANITY() rc = rtCrX509Name_CheckSanityExtra(pThis, fFlags, pErrInfo, pszErrorTag) +RTASN1TMPL_SEQ_OF(RTCRX509RELATIVEDISTINGUISHEDNAME, RTCrX509RelativeDistinguishedName); +#undef RTASN1TMPL_SET_SEQ_EXEC_CHECK_SANITY +#define RTASN1TMPL_SET_SEQ_EXEC_CHECK_SANITY() do { } while (0) +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + +/* + * One X.509 OtherName. + * Note! This is simplified and might not work correctly for all types with + * non-DER compatible encodings. + */ +#define RTASN1TMPL_TYPE RTCRX509OTHERNAME +#define RTASN1TMPL_EXT_NAME RTCrX509OtherName +#define RTASN1TMPL_INT_NAME rtCrX509OtherName +RTASN1TMPL_BEGIN_SEQCORE(); +RTASN1TMPL_MEMBER( TypeId, RTASN1OBJID, RTAsn1ObjId); +RTASN1TMPL_MEMBER( Value, RTASN1DYNTYPE, RTAsn1DynType); +RTASN1TMPL_END_SEQCORE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * One X.509 GeneralName. + * Note! This is simplified and might not work correctly for all types with + * non-DER compatible encodings. + */ +#define RTASN1TMPL_TYPE RTCRX509GENERALNAME +#define RTASN1TMPL_EXT_NAME RTCrX509GeneralName +#define RTASN1TMPL_INT_NAME rtCrX509GeneralName +RTASN1TMPL_BEGIN_PCHOICE(); +RTASN1TMPL_PCHOICE_ITAG( 0, RTCRX509GENERALNAMECHOICE_OTHER_NAME, u.pT0_OtherName, OtherName, RTCRX509OTHERNAME, RTCrX509OtherName); +RTASN1TMPL_PCHOICE_ITAG_CP( 1, RTCRX509GENERALNAMECHOICE_RFC822_NAME, u.pT1_Rfc822, Rfc822, RTASN1STRING, RTAsn1Ia5String); +RTASN1TMPL_PCHOICE_ITAG_CP( 2, RTCRX509GENERALNAMECHOICE_DNS_NAME, u.pT2_DnsName, DnsType, RTASN1STRING, RTAsn1Ia5String); +RTASN1TMPL_PCHOICE_XTAG( 3, RTCRX509GENERALNAMECHOICE_X400_ADDRESS, u.pT3, CtxTag3, X400Address, RTASN1DYNTYPE, RTAsn1DynType); /** @todo */ +RTASN1TMPL_PCHOICE_XTAG( 4, RTCRX509GENERALNAMECHOICE_DIRECTORY_NAME, u.pT4, CtxTag4, DirectoryName, RTCRX509NAME, RTCrX509Name); +RTASN1TMPL_PCHOICE_XTAG( 5, RTCRX509GENERALNAMECHOICE_EDI_PARTY_NAME, u.pT5, CtxTag5, EdiPartyName, RTASN1DYNTYPE, RTAsn1DynType); /** @todo */ +RTASN1TMPL_PCHOICE_ITAG_CP( 6, RTCRX509GENERALNAMECHOICE_URI, u.pT6_Uri, Uri, RTASN1STRING, RTAsn1Ia5String); +RTASN1TMPL_PCHOICE_ITAG_CP( 7, RTCRX509GENERALNAMECHOICE_IP_ADDRESS, u.pT7_IpAddress, IpAddress, RTASN1OCTETSTRING, RTAsn1OctetString); /** @todo Constraints */ +RTASN1TMPL_PCHOICE_ITAG_CP( 8, RTCRX509GENERALNAMECHOICE_REGISTERED_ID, u.pT8_RegisteredId,RegisteredId,RTASN1OBJID, RTAsn1ObjId); +RTASN1TMPL_END_PCHOICE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * Sequence of X.509 GeneralNames. + */ +#define RTASN1TMPL_TYPE RTCRX509GENERALNAMES +#define RTASN1TMPL_EXT_NAME RTCrX509GeneralNames +#define RTASN1TMPL_INT_NAME rtCrX509GeneralNames +RTASN1TMPL_SEQ_OF(RTCRX509GENERALNAME, RTCrX509GeneralName); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * X.509 UniqueIdentifier - RTASN1BITSTRING alias. + */ + + +/* + * X.509 SubjectPublicKeyInfo. + */ +#define RTASN1TMPL_TYPE RTCRX509SUBJECTPUBLICKEYINFO +#define RTASN1TMPL_EXT_NAME RTCrX509SubjectPublicKeyInfo +#define RTASN1TMPL_INT_NAME rtCrX509SubjectPublicKeyInfo +RTASN1TMPL_BEGIN_SEQCORE(); +RTASN1TMPL_MEMBER( Algorithm, RTCRX509ALGORITHMIDENTIFIER, RTCrX509AlgorithmIdentifier); +RTASN1TMPL_MEMBER( SubjectPublicKey, RTASN1BITSTRING, RTAsn1BitString); +RTASN1TMPL_EXEC_CHECK_SANITY( rc = rtCrX509SubjectPublicKeyInfo_CheckSanityExtra(pThis, fFlags, pErrInfo, pszErrorTag) ) +RTASN1TMPL_END_SEQCORE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * X.509 AuthorityKeyIdentifier (IPRT representation). + */ +#define RTASN1TMPL_TYPE RTCRX509AUTHORITYKEYIDENTIFIER +#define RTASN1TMPL_EXT_NAME RTCrX509AuthorityKeyIdentifier +#define RTASN1TMPL_INT_NAME rtCrX509AuthorityKeyIdentifier +RTASN1TMPL_BEGIN_SEQCORE(); +RTASN1TMPL_MEMBER_OPT_ITAG_CP( KeyIdentifier, RTASN1OCTETSTRING, RTAsn1OctetString, 0); +RTASN1TMPL_MEMBER_OPT_ITAG( AuthorityCertIssuer, RTCRX509GENERALNAMES, RTCrX509GeneralNames, 1); +RTASN1TMPL_MEMBER_OPT_ITAG_CP( AuthorityCertSerialNumber, RTASN1INTEGER, RTAsn1Integer, 2); +RTASN1TMPL_END_SEQCORE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * X.509 OldAuthorityKeyIdentifier (IPRT representation). + */ +#define RTASN1TMPL_TYPE RTCRX509OLDAUTHORITYKEYIDENTIFIER +#define RTASN1TMPL_EXT_NAME RTCrX509OldAuthorityKeyIdentifier +#define RTASN1TMPL_INT_NAME rtCrX509OldAuthorityKeyIdentifier +RTASN1TMPL_BEGIN_SEQCORE(); +RTASN1TMPL_MEMBER_OPT_ITAG_CP( KeyIdentifier, RTASN1OCTETSTRING, RTAsn1OctetString, 0); +RTASN1TMPL_MEMBER_OPT_XTAG( T1, CtxTag1, AuthorityCertIssuer, RTCRX509NAME, RTCrX509Name, 1); +RTASN1TMPL_MEMBER_OPT_ITAG_CP( AuthorityCertSerialNumber, RTASN1INTEGER, RTAsn1Integer, 2); +RTASN1TMPL_END_SEQCORE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * One X.509 PolicyQualifierInfo. + */ +#define RTASN1TMPL_TYPE RTCRX509POLICYQUALIFIERINFO +#define RTASN1TMPL_EXT_NAME RTCrX509PolicyQualifierInfo +#define RTASN1TMPL_INT_NAME rtCrX509PolicyQualifierInfo +RTASN1TMPL_BEGIN_SEQCORE(); +RTASN1TMPL_MEMBER( PolicyQualifierId, RTASN1OBJID, RTAsn1ObjId); +RTASN1TMPL_MEMBER( Qualifier, RTASN1DYNTYPE, RTAsn1DynType); +RTASN1TMPL_END_SEQCORE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * Sequence of X.509 PolicyQualifierInfo. + */ +#define RTASN1TMPL_TYPE RTCRX509POLICYQUALIFIERINFOS +#define RTASN1TMPL_EXT_NAME RTCrX509PolicyQualifierInfos +#define RTASN1TMPL_INT_NAME rtCrX509PolicyQualifierInfos +RTASN1TMPL_SEQ_OF(RTCRX509POLICYQUALIFIERINFO, RTCrX509PolicyQualifierInfo); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * One X.509 PolicyInformation. + */ +#define RTASN1TMPL_TYPE RTCRX509POLICYINFORMATION +#define RTASN1TMPL_EXT_NAME RTCrX509PolicyInformation +#define RTASN1TMPL_INT_NAME rtCrX509PolicyInformation +RTASN1TMPL_BEGIN_SEQCORE(); +RTASN1TMPL_MEMBER( PolicyIdentifier, RTASN1OBJID, RTAsn1ObjId); +RTASN1TMPL_MEMBER_OPT_ITAG_UC( PolicyQualifiers, RTCRX509POLICYQUALIFIERINFOS, RTCrX509PolicyQualifierInfos, ASN1_TAG_SEQUENCE); +RTASN1TMPL_END_SEQCORE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * Sequence of X.509 CertificatePolicies. + */ +#define RTASN1TMPL_TYPE RTCRX509CERTIFICATEPOLICIES +#define RTASN1TMPL_EXT_NAME RTCrX509CertificatePolicies +#define RTASN1TMPL_INT_NAME rtCrX509CertificatePolicies +RTASN1TMPL_SEQ_OF(RTCRX509POLICYINFORMATION, RTCrX509PolicyInformation); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * One X.509 PolicyMapping (IPRT representation). + */ +#define RTASN1TMPL_TYPE RTCRX509POLICYMAPPING +#define RTASN1TMPL_EXT_NAME RTCrX509PolicyMapping +#define RTASN1TMPL_INT_NAME rtCrX509PolicyMapping +RTASN1TMPL_BEGIN_SEQCORE(); +RTASN1TMPL_MEMBER( IssuerDomainPolicy, RTASN1OBJID, RTAsn1ObjId); +RTASN1TMPL_MEMBER( SubjectDomainPolicy, RTASN1OBJID, RTAsn1ObjId); +RTASN1TMPL_END_SEQCORE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * Sequence of X.509 PolicyMappings (IPRT representation). + */ +#define RTASN1TMPL_TYPE RTCRX509POLICYMAPPINGS +#define RTASN1TMPL_EXT_NAME RTCrX509PolicyMappings +#define RTASN1TMPL_INT_NAME rtCrX509PolicyMappings +RTASN1TMPL_SEQ_OF(RTCRX509POLICYMAPPING, RTCrX509PolicyMapping); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * X.509 BasicConstraints (IPRT representation). + */ +#define RTASN1TMPL_TYPE RTCRX509BASICCONSTRAINTS +#define RTASN1TMPL_EXT_NAME RTCrX509BasicConstraints +#define RTASN1TMPL_INT_NAME rtCrX509BasicConstraints +RTASN1TMPL_BEGIN_SEQCORE(); +RTASN1TMPL_MEMBER_DEF_ITAG_UP( CA, RTASN1BOOLEAN, RTAsn1Boolean, ASN1_TAG_BOOLEAN, false); +RTASN1TMPL_MEMBER_OPT_ITAG_UP( PathLenConstraint, RTASN1INTEGER, RTAsn1Integer, ASN1_TAG_INTEGER); +RTASN1TMPL_END_SEQCORE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * X.509 GeneralSubtree (IPRT representation). + */ +#define RTASN1TMPL_TYPE RTCRX509GENERALSUBTREE +#define RTASN1TMPL_EXT_NAME RTCrX509GeneralSubtree +#define RTASN1TMPL_INT_NAME rtCrX509GeneralSubtree +RTASN1TMPL_BEGIN_SEQCORE(); +RTASN1TMPL_MEMBER( Base, RTCRX509GENERALNAME, RTCrX509GeneralName); +RTASN1TMPL_MEMBER_DEF_ITAG_UP( Minimum, RTASN1INTEGER, RTAsn1Integer, ASN1_TAG_INTEGER, 0); +RTASN1TMPL_MEMBER_OPT_ITAG_UP( Maximum, RTASN1INTEGER, RTAsn1Integer, ASN1_TAG_INTEGER); +RTASN1TMPL_END_SEQCORE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + + +/* + * Sequence of X.509 GeneralSubtrees (IPRT representation). + */ +#define RTASN1TMPL_TYPE RTCRX509GENERALSUBTREES +#define RTASN1TMPL_EXT_NAME RTCrX509GeneralSubtrees +#define RTASN1TMPL_INT_NAME rtCrX509GeneralSubtrees +RTASN1TMPL_SEQ_OF(RTCRX509GENERALSUBTREE, RTCrX509GeneralSubtree); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * X.509 NameConstraints (IPRT representation). + */ +#define RTASN1TMPL_TYPE RTCRX509NAMECONSTRAINTS +#define RTASN1TMPL_EXT_NAME RTCrX509NameConstraints +#define RTASN1TMPL_INT_NAME rtCrX509NameConstraints +RTASN1TMPL_BEGIN_SEQCORE(); +RTASN1TMPL_MEMBER_OPT_XTAG( T0, CtxTag0, PermittedSubtrees, RTCRX509GENERALSUBTREES, RTCrX509GeneralSubtrees, 0); +RTASN1TMPL_MEMBER_OPT_XTAG( T1, CtxTag1, ExcludedSubtrees, RTCRX509GENERALSUBTREES, RTCrX509GeneralSubtrees, 1); +RTASN1TMPL_END_SEQCORE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * X.509 PolicyConstraints (IPRT representation). + */ +#define RTASN1TMPL_TYPE RTCRX509POLICYCONSTRAINTS +#define RTASN1TMPL_EXT_NAME RTCrX509PolicyConstraints +#define RTASN1TMPL_INT_NAME rtCrX509PolicyConstraints +RTASN1TMPL_BEGIN_SEQCORE(); +RTASN1TMPL_MEMBER_OPT_ITAG_CP( RequireExplicitPolicy, RTASN1INTEGER, RTAsn1Integer, 0); +RTASN1TMPL_MEMBER_OPT_ITAG_CP( InhibitPolicyMapping, RTASN1INTEGER, RTAsn1Integer, 1); +RTASN1TMPL_END_SEQCORE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * One X.509 Extension. + */ +#define RTASN1TMPL_TYPE RTCRX509EXTENSION +#define RTASN1TMPL_EXT_NAME RTCrX509Extension +#define RTASN1TMPL_INT_NAME rtCrX509Extension +RTASN1TMPL_BEGIN_SEQCORE(); +RTASN1TMPL_MEMBER( ExtnId, RTASN1OBJID, RTAsn1ObjId); +RTASN1TMPL_MEMBER_DEF_ITAG_UP( Critical, RTASN1BOOLEAN, RTAsn1Boolean, ASN1_TAG_BOOLEAN, false); +RTASN1TMPL_MEMBER( ExtnValue, RTASN1OCTETSTRING, RTAsn1OctetString); +RTASN1TMPL_EXEC_DECODE(rc = RTCrX509Extension_ExtnValue_DecodeAsn1(pCursor, fFlags, pThis, "ExtnValue")) +RTASN1TMPL_EXEC_CLONE( rc = rtCrX509Extension_ExtnValue_Clone(pThis, pSrc)) +RTASN1TMPL_END_SEQCORE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * Sequence of X.509 Extensions. + */ +#define RTASN1TMPL_TYPE RTCRX509EXTENSIONS +#define RTASN1TMPL_EXT_NAME RTCrX509Extensions +#define RTASN1TMPL_INT_NAME rtCrX509Extensions +RTASN1TMPL_SEQ_OF(RTCRX509EXTENSION, RTCrX509Extension); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * X.509 TbsCertificate. + */ +#define RTASN1TMPL_TYPE RTCRX509TBSCERTIFICATE +#define RTASN1TMPL_EXT_NAME RTCrX509TbsCertificate +#define RTASN1TMPL_INT_NAME rtCrX509TbsCertificate +RTASN1TMPL_BEGIN_SEQCORE(); +RTASN1TMPL_MEMBER_OPT_XTAG( T0, CtxTag0, Version, RTASN1INTEGER, RTAsn1Integer, 0); +RTASN1TMPL_MEMBER( SerialNumber, RTASN1INTEGER, RTAsn1Integer); +RTASN1TMPL_MEMBER( Signature, RTCRX509ALGORITHMIDENTIFIER, RTCrX509AlgorithmIdentifier); +RTASN1TMPL_MEMBER( Issuer, RTCRX509NAME, RTCrX509Name); +RTASN1TMPL_MEMBER( Validity, RTCRX509VALIDITY, RTCrX509Validity); +RTASN1TMPL_MEMBER( Subject, RTCRX509NAME, RTCrX509Name); +RTASN1TMPL_MEMBER( SubjectPublicKeyInfo, RTCRX509SUBJECTPUBLICKEYINFO, RTCrX509SubjectPublicKeyInfo); +RTASN1TMPL_MEMBER_OPT_XTAG( T1, CtxTag1, IssuerUniqueId, RTCRX509UNIQUEIDENTIFIER, RTCrX509UniqueIdentifier, 1); +RTASN1TMPL_MEMBER_OPT_XTAG( T2, CtxTag2, SubjectUniqueId, RTCRX509UNIQUEIDENTIFIER, RTCrX509UniqueIdentifier, 2); +RTASN1TMPL_MEMBER_OPT_XTAG( T3, CtxTag3, Extensions, RTCRX509EXTENSIONS, RTCrX509Extensions, 3); +RTASN1TMPL_EXEC_DECODE( rc = RTCrX509TbsCertificate_ReprocessExtensions(pThis, pCursor->pPrimary->pErrInfo) ) +RTASN1TMPL_EXEC_CLONE( rc = RTCrX509TbsCertificate_ReprocessExtensions(pThis, NULL) ) +RTASN1TMPL_EXEC_CHECK_SANITY( rc = rtCrX509TbsCertificate_CheckSanityExtra(pThis, fFlags, pErrInfo, pszErrorTag) ) +RTASN1TMPL_END_SEQCORE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * One X.509 Certificate. + */ +#define RTASN1TMPL_TYPE RTCRX509CERTIFICATE +#define RTASN1TMPL_EXT_NAME RTCrX509Certificate +#define RTASN1TMPL_INT_NAME rtCrX509Certificate +RTASN1TMPL_BEGIN_SEQCORE(); +RTASN1TMPL_MEMBER( TbsCertificate, RTCRX509TBSCERTIFICATE, RTCrX509TbsCertificate); +RTASN1TMPL_MEMBER( SignatureAlgorithm, RTCRX509ALGORITHMIDENTIFIER, RTCrX509AlgorithmIdentifier); +RTASN1TMPL_MEMBER( SignatureValue, RTASN1BITSTRING, RTAsn1BitString); +RTASN1TMPL_EXEC_CHECK_SANITY( rc = rtCrX509Certificate_CheckSanityExtra(pThis, fFlags, pErrInfo, pszErrorTag) ) +RTASN1TMPL_END_SEQCORE(); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + + +/* + * Set of X.509 Certificates. + */ +/** @todo Microsoft Hacks. ExtendedCertificates. */ +#define RTASN1TMPL_TYPE RTCRX509CERTIFICATES +#define RTASN1TMPL_EXT_NAME RTCrX509Certificates +#define RTASN1TMPL_INT_NAME rtCrX509Certificates +RTASN1TMPL_SET_OF(RTCRX509CERTIFICATE, RTCrX509Certificate); +#undef RTASN1TMPL_TYPE +#undef RTASN1TMPL_EXT_NAME +#undef RTASN1TMPL_INT_NAME + diff --git a/src/VBox/Runtime/common/crypto/x509-verify.cpp b/src/VBox/Runtime/common/crypto/x509-verify.cpp new file mode 100644 index 00000000..32c8f953 --- /dev/null +++ b/src/VBox/Runtime/common/crypto/x509-verify.cpp @@ -0,0 +1,139 @@ +/* $Id: x509-verify.cpp $ */ +/** @file + * IPRT - Crypto - X.509, Signature verification. + */ + +/* + * Copyright (C) 2006-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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include <iprt/crypto/x509.h> +#include <iprt/crypto/pkix.h> +#include <iprt/crypto/key.h> + +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/string.h> + + +RTDECL(int) RTCrX509Certificate_VerifySignature(PCRTCRX509CERTIFICATE pThis, PCRTASN1OBJID pAlgorithm, + PCRTASN1DYNTYPE pParameters, PCRTASN1BITSTRING pPublicKey, + PRTERRINFO pErrInfo) +{ + /* + * Validate the input a little. + */ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertReturn(RTCrX509Certificate_IsPresent(pThis), VERR_INVALID_PARAMETER); + + AssertPtrReturn(pAlgorithm, VERR_INVALID_POINTER); + AssertReturn(RTAsn1ObjId_IsPresent(pAlgorithm), VERR_INVALID_POINTER); + + if (pParameters) + { + AssertPtrReturn(pParameters, VERR_INVALID_POINTER); + if (pParameters->enmType == RTASN1TYPE_NULL) + pParameters = NULL; + } + + AssertPtrReturn(pPublicKey, VERR_INVALID_POINTER); + AssertReturn(RTAsn1BitString_IsPresent(pPublicKey), VERR_INVALID_POINTER); + + /* + * Check if the algorithm matches. + */ + const char *pszCipherOid = RTCrPkixGetCiperOidFromSignatureAlgorithm(&pThis->SignatureAlgorithm.Algorithm); + if (!pszCipherOid) + return RTErrInfoSetF(pErrInfo, VERR_CR_X509_UNKNOWN_CERT_SIGN_ALGO, + "Certificate signature algorithm not known: %s", + pThis->SignatureAlgorithm.Algorithm.szObjId); + + if (RTAsn1ObjId_CompareWithString(pAlgorithm, pszCipherOid) != 0) + return RTErrInfoSetF(pErrInfo, VERR_CR_X509_CERT_SIGN_ALGO_MISMATCH, + "Certificate signature cipher algorithm mismatch: cert uses %s (%s) while key uses %s", + pszCipherOid, pThis->SignatureAlgorithm.Algorithm.szObjId, pAlgorithm->szObjId); + + /* + * Wrap up the public key. + */ + RTCRKEY hPubKey; + int rc = RTCrKeyCreateFromPublicAlgorithmAndBits(&hPubKey, pAlgorithm, pPublicKey, pErrInfo, NULL); + if (RT_FAILURE(rc)) + return rc; + + /* + * Here we should recode the to-be-signed part as DER, but we'll ASSUME + * that it's already in DER encoding and only does this if there the + * encoded bits are missing. + */ + const uint8_t *pbRaw; + uint32_t cbRaw; + void *pvFree = NULL; + rc = RTAsn1EncodeQueryRawBits(RTCrX509TbsCertificate_GetAsn1Core(&pThis->TbsCertificate), &pbRaw, &cbRaw, &pvFree, pErrInfo); + if (RT_SUCCESS(rc)) + { + rc = RTCrPkixPubKeyVerifySignature(&pThis->SignatureAlgorithm.Algorithm, hPubKey, pParameters, &pThis->SignatureValue, + pbRaw, cbRaw, pErrInfo); + RTMemTmpFree(pvFree); + } + + /* Free the public key. */ + uint32_t cRefs = RTCrKeyRelease(hPubKey); + Assert(cRefs == 0); NOREF(cRefs); + + return rc; +} + + +RTDECL(int) RTCrX509Certificate_VerifySignatureSelfSigned(PCRTCRX509CERTIFICATE pThis, PRTERRINFO pErrInfo) +{ + /* + * Validate the input a little. + */ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertReturn(RTCrX509Certificate_IsPresent(pThis), VERR_INVALID_PARAMETER); + + /* + * Assemble parameters for the generic verification call. + */ + PCRTCRX509TBSCERTIFICATE const pTbsCert = &pThis->TbsCertificate; + PCRTASN1DYNTYPE pParameters = NULL; + if ( RTASN1CORE_IS_PRESENT(&pTbsCert->SubjectPublicKeyInfo.Algorithm.Parameters.u.Core) + && pTbsCert->SubjectPublicKeyInfo.Algorithm.Parameters.enmType != RTASN1TYPE_NULL) + pParameters = &pTbsCert->SubjectPublicKeyInfo.Algorithm.Parameters; + return RTCrX509Certificate_VerifySignature(pThis, &pTbsCert->SubjectPublicKeyInfo.Algorithm.Algorithm, pParameters, + &pTbsCert->SubjectPublicKeyInfo.SubjectPublicKey, pErrInfo); +} + |