mirror of https://github.com/wolfSSL/wolfssh.git
550 lines
15 KiB
C
550 lines
15 KiB
C
/* certman.c
|
|
*
|
|
* Copyright (C) 2014-2023 wolfSSL Inc.
|
|
*
|
|
* This file is part of wolfSSH.
|
|
*
|
|
* wolfSSH 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; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* wolfSSH 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 wolfSSH. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
|
|
/*
|
|
* The certman module contains utility functions wrapping the wolfSSL
|
|
* certificate manager functions to validate user certificates.
|
|
*/
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#ifdef WOLFSSL_USER_SETTINGS
|
|
#include <wolfssl/wolfcrypt/settings.h>
|
|
#else
|
|
#include <wolfssl/options.h>
|
|
#endif
|
|
|
|
|
|
#include <wolfssl/ssl.h>
|
|
#include <wolfssl/ocsp.h>
|
|
#include <wolfssl/wolfcrypt/error-crypt.h>
|
|
#include <wolfssl/error-ssl.h>
|
|
|
|
#include <wolfssh/internal.h>
|
|
#include <wolfssh/certman.h>
|
|
|
|
|
|
#ifdef WOLFSSH_CERTS
|
|
|
|
#ifdef NO_INLINE
|
|
#include <wolfssh/misc.h>
|
|
#else
|
|
#define WOLFSSH_MISC_INCLUDED
|
|
#include "src/misc.c"
|
|
#endif
|
|
|
|
|
|
#define WLOG_ENTER() do { \
|
|
WLOG(WS_LOG_CERTMAN, "Entering %s()", __func__); \
|
|
} while (0)
|
|
|
|
#define WLOG_LEAVE(x) do { \
|
|
WLOG(WS_LOG_CERTMAN, "Leaving %s(), ret = %d", __func__, (x)); \
|
|
} while (0)
|
|
|
|
#define WLOG_LEAVE_VOID() do { \
|
|
WLOG(WS_LOG_CERTMAN, "Leaving %s()", __func__); \
|
|
} while (0)
|
|
|
|
#define WLOG_LEAVE_PTR(x) do { \
|
|
WLOG(WS_LOG_CERTMAN, "Leaving %s(), ret = %p", __func__, (x)); \
|
|
} while (0)
|
|
|
|
#ifdef DEBUG_WOLFSSH
|
|
#define DUMP(x,y) do { DumpOctetString((x),(y)); } while (0)
|
|
#else
|
|
#define DUMP(x,y)
|
|
#endif
|
|
|
|
|
|
struct WOLFSSH_CERTMAN {
|
|
void* heap;
|
|
WOLFSSL_CERT_MANAGER* cm;
|
|
};
|
|
|
|
|
|
static WOLFSSH_CERTMAN* _CertMan_init(WOLFSSH_CERTMAN* cm, void* heap)
|
|
{
|
|
WOLFSSH_CERTMAN* ret = NULL;
|
|
WLOG_ENTER();
|
|
|
|
ret = cm;
|
|
if (ret != NULL) {
|
|
WMEMSET(ret, 0, sizeof(WOLFSSH_CERTMAN));
|
|
ret->cm = wolfSSL_CertManagerNew_ex(heap);
|
|
if (ret->cm == NULL) {
|
|
ret = NULL;
|
|
}
|
|
#ifdef HAVE_OCSP
|
|
else {
|
|
int err;
|
|
|
|
err = wolfSSL_CertManagerEnableOCSP(ret->cm,
|
|
WOLFSSL_OCSP_CHECKALL);
|
|
if (err == WOLFSSL_SUCCESS) {
|
|
WLOG(WS_LOG_CERTMAN, "Enabled OCSP");
|
|
}
|
|
else {
|
|
WLOG(WS_LOG_CERTMAN, "Couldn't enable OCSP");
|
|
wolfSSL_CertManagerFree(ret->cm);
|
|
ret = NULL;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
WLOG_LEAVE_PTR(ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static void _CertMan_ResourceFree(WOLFSSH_CERTMAN* cm, void* heap)
|
|
{
|
|
WOLFSSH_UNUSED(heap);
|
|
WLOG_ENTER();
|
|
|
|
if (cm != NULL) {
|
|
if (cm->cm != NULL) {
|
|
wolfSSL_CertManagerFree(cm->cm);
|
|
}
|
|
WMEMSET(cm, 0, sizeof *cm);
|
|
}
|
|
|
|
WLOG_LEAVE_VOID();
|
|
}
|
|
|
|
|
|
WOLFSSH_CERTMAN* wolfSSH_CERTMAN_new(void* heap)
|
|
{
|
|
WOLFSSH_CERTMAN* cm = NULL;
|
|
|
|
WLOG_ENTER();
|
|
|
|
cm = (WOLFSSH_CERTMAN*)WMALLOC(sizeof *cm, heap, DYNTYPE_CERTMAN);
|
|
if (cm != NULL) {
|
|
if (_CertMan_init(cm, heap) == NULL) {
|
|
WFREE(cm, heap, DYNTYPE_CERTMAN);
|
|
cm = NULL;
|
|
}
|
|
}
|
|
|
|
WLOG_LEAVE_PTR(cm);
|
|
return cm;
|
|
}
|
|
|
|
|
|
void wolfSSH_CERTMAN_free(WOLFSSH_CERTMAN* cm)
|
|
{
|
|
WLOG_ENTER();
|
|
|
|
if (cm) {
|
|
void* heap = cm->heap;
|
|
|
|
_CertMan_ResourceFree(cm, heap);
|
|
WFREE(cm, heap, DYNTYPE_CERTMAN);
|
|
}
|
|
|
|
WLOG_LEAVE_VOID();
|
|
}
|
|
|
|
|
|
/* return WS_SUCCESS on success */
|
|
int wolfSSH_CERTMAN_LoadRootCA_buffer(WOLFSSH_CERTMAN* cm,
|
|
const unsigned char* rootCa, word32 rootCaSz)
|
|
{
|
|
int ret;
|
|
|
|
WLOG_ENTER();
|
|
|
|
ret = wolfSSL_CertManagerLoadCABuffer(cm->cm, rootCa, rootCaSz,
|
|
WOLFSSL_FILETYPE_ASN1);
|
|
if (ret == WOLFSSL_SUCCESS) {
|
|
ret = WS_SUCCESS;
|
|
}
|
|
|
|
WLOG_LEAVE(ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
#ifndef WOLFSSH_NO_FPKI
|
|
static int CheckProfile(DecodedCert* cert, int profile);
|
|
enum {
|
|
PROFILE_FPKI_WORKSHEET_6 = 6,
|
|
PROFILE_FPKI_WORKSHEET_10 = 10,
|
|
PROFILE_FPKI_WORKSHEET_16 = 16
|
|
};
|
|
#endif /* WOLFSSH_NO_FPKI */
|
|
|
|
/* if max chain depth not set in wolfSSL then default to 9 */
|
|
#ifndef MAX_CHAIN_DEPTH
|
|
#define MAX_CHAIN_DEPTH 9
|
|
#endif
|
|
|
|
/* if handling a chain it is expected to be the leaf cert first followed by
|
|
* intermediates and CA last (CA may be ommited) */
|
|
int wolfSSH_CERTMAN_VerifyCerts_buffer(WOLFSSH_CERTMAN* cm,
|
|
const unsigned char* certs, word32 certSz, word32 certsCount)
|
|
{
|
|
int ret = WS_SUCCESS;
|
|
|
|
word32 idx = 0;
|
|
int certIdx = 0;
|
|
unsigned char **certLoc; /* locations of certificate start */
|
|
word32 *certLen; /* size of certificate, in sync with certLoc */
|
|
|
|
WLOG_ENTER();
|
|
|
|
if (cm == NULL || certs == NULL) {
|
|
return WS_BAD_ARGUMENT;
|
|
}
|
|
|
|
if (certsCount > MAX_CHAIN_DEPTH) {
|
|
WLOG(WS_LOG_CERTMAN, "cert count is larger than MAX_CHAIN_DEPTH");
|
|
return WS_BAD_ARGUMENT;
|
|
}
|
|
|
|
certLoc = (unsigned char**)WMALLOC(certsCount * sizeof(unsigned char*),
|
|
cm->heap, DYNTYPE_CERT);
|
|
certLen = (word32*)WMALLOC(certsCount * sizeof(word32), cm->heap,
|
|
DYNTYPE_CERT);
|
|
if (certLoc == NULL || certLen == NULL) {
|
|
ret = WS_MEMORY_E;
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
for (certIdx = 0; certIdx < (int)certsCount; certIdx++) {
|
|
word32 sz = 0;
|
|
|
|
if ((idx + UINT32_SZ) > certSz) {
|
|
WLOG(WS_LOG_CERTMAN, "cert count is past end of buffer");
|
|
ret = ASN_PARSE_E;
|
|
break;
|
|
}
|
|
|
|
certLoc[certIdx] = (byte*)certs + idx + UINT32_SZ;
|
|
|
|
/* get the size of the certificate */
|
|
ret = GetSize(&sz, certs, certSz, &idx);
|
|
|
|
/* advance current pointer and update current total size */
|
|
if (ret == WS_SUCCESS) {
|
|
certLen[certIdx] = sz;
|
|
if (idx + sz > certSz) {
|
|
WLOG(WS_LOG_CERTMAN, "cert found is too large!");
|
|
ret = ASN_PARSE_E;
|
|
break;
|
|
}
|
|
idx += sz;
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ret == WS_SUCCESS) {
|
|
for (certIdx = certsCount - 1; certIdx >= 0; certIdx--) {
|
|
WLOG(WS_LOG_CERTMAN, "verifying cert at index %d", certIdx);
|
|
ret = wolfSSL_CertManagerVerifyBuffer(cm->cm, certLoc[certIdx],
|
|
certLen[certIdx], WOLFSSL_FILETYPE_ASN1);
|
|
if (ret == WOLFSSL_SUCCESS) {
|
|
ret = WS_SUCCESS;
|
|
}
|
|
else if (ret == ASN_NO_SIGNER_E) {
|
|
WLOG(WS_LOG_CERTMAN, "cert verify: no signer");
|
|
ret = WS_CERT_NO_SIGNER_E;
|
|
}
|
|
else if (ret == ASN_AFTER_DATE_E) {
|
|
WLOG(WS_LOG_CERTMAN, "cert verify: expired");
|
|
ret = WS_CERT_EXPIRED_E;
|
|
}
|
|
else if (ret == ASN_SIG_CONFIRM_E) {
|
|
WLOG(WS_LOG_CERTMAN, "cert verify: bad sig");
|
|
ret = WS_CERT_SIG_CONFIRM_E;
|
|
}
|
|
else {
|
|
WLOG(WS_LOG_CERTMAN, "cert verify: other error (%d)", ret);
|
|
ret = WS_CERT_OTHER_E;
|
|
}
|
|
|
|
#ifdef HAVE_OCSP
|
|
if (ret == WS_SUCCESS) {
|
|
ret = wolfSSL_CertManagerCheckOCSP(cm->cm, (byte*)certLoc[certIdx],
|
|
certLen[certIdx]);
|
|
|
|
if (ret == WOLFSSL_SUCCESS) {
|
|
ret = WS_SUCCESS;
|
|
}
|
|
else if (ret == OCSP_CERT_REVOKED) {
|
|
WLOG(WS_LOG_CERTMAN, "ocsp lookup: ocsp revoked");
|
|
ret = WS_CERT_REVOKED_E;
|
|
}
|
|
else {
|
|
WLOG(WS_LOG_CERTMAN, "ocsp lookup: other error (%d)", ret);
|
|
ret = WS_CERT_OTHER_E;
|
|
}
|
|
}
|
|
#endif /* HAVE_OCSP */
|
|
|
|
/* verified successfully, add intermideate as trusted */
|
|
if (ret == WS_SUCCESS && certIdx > 0) {
|
|
WLOG(WS_LOG_CERTMAN, "adding intermidiate cert as trusted");
|
|
ret = wolfSSH_CERTMAN_LoadRootCA_buffer(cm, certLoc[certIdx],
|
|
certLen[certIdx]);
|
|
}
|
|
|
|
if (ret != WS_SUCCESS) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifndef WOLFSSH_NO_FPKI
|
|
/* FPKI checking on the leaf certificate */
|
|
if (ret == WS_SUCCESS) {
|
|
DecodedCert decoded;
|
|
|
|
wc_InitDecodedCert(&decoded, certLoc[0], certLen[0], cm->cm);
|
|
ret = wc_ParseCert(&decoded, WOLFSSL_FILETYPE_ASN1, 0, cm->cm);
|
|
|
|
if (ret == 0) {
|
|
ret =
|
|
CheckProfile(&decoded, PROFILE_FPKI_WORKSHEET_6) ||
|
|
CheckProfile(&decoded, PROFILE_FPKI_WORKSHEET_10) ||
|
|
CheckProfile(&decoded, PROFILE_FPKI_WORKSHEET_16);
|
|
|
|
if (ret == 0) {
|
|
WLOG(WS_LOG_CERTMAN, "certificate didn't match profile");
|
|
ret = WS_CERT_PROFILE_E;
|
|
}
|
|
else {
|
|
ret = WS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
FreeDecodedCert(&decoded);
|
|
}
|
|
#endif /* WOLFSSH_NO_FPKI */
|
|
|
|
if (certLoc != NULL)
|
|
WFREE(certLoc, cm->heap, DYNTYPE_CERT);
|
|
if (certLen != NULL)
|
|
WFREE(certLen, cm->heap, DYNTYPE_CERT);
|
|
WLOG_LEAVE(ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
#ifndef WOLFSSH_NO_FPKI
|
|
static int CheckProfile(DecodedCert* cert, int profile)
|
|
{
|
|
int valid = (cert != NULL);
|
|
const char* certPolicies[2] = {NULL, NULL};
|
|
byte extKeyUsage = 0, extKeyUsageSsh = 0;
|
|
|
|
if (profile == PROFILE_FPKI_WORKSHEET_6) {
|
|
certPolicies[0] = "2.16.840.1.101.3.2.1.3.13";
|
|
extKeyUsage = EXTKEYUSE_CLIENT_AUTH;
|
|
extKeyUsageSsh = EXTKEYUSE_SSH_MSCL;
|
|
}
|
|
else if (profile == PROFILE_FPKI_WORKSHEET_10) {
|
|
certPolicies[0] = "2.16.840.1.101.3.2.1.3.40";
|
|
certPolicies[1] = "2.16.840.1.101.3.2.1.3.41";
|
|
extKeyUsage = EXTKEYUSE_CLIENT_AUTH;
|
|
}
|
|
else if (profile == PROFILE_FPKI_WORKSHEET_16) {
|
|
certPolicies[0] = "2.16.840.1.101.3.2.1.3.45";
|
|
extKeyUsage = EXTKEYUSE_CLIENT_AUTH;
|
|
extKeyUsageSsh = EXTKEYUSE_SSH_MSCL;
|
|
}
|
|
else {
|
|
valid = 0;
|
|
}
|
|
|
|
if (valid) {
|
|
valid = cert->extKeyUsageSet &&
|
|
cert->extKeyUsage == KEYUSE_DIGITAL_SIG &&
|
|
/*cert->extBasicConstCrit;*/ 1;
|
|
}
|
|
|
|
if (valid) {
|
|
valid = WSTRCMP(cert->countryOfCitizenship, "US") == 0;
|
|
if (valid != 1)
|
|
WLOG(WS_LOG_CERTMAN, "cert contry of citizenship invalid");
|
|
}
|
|
|
|
if (valid) {
|
|
valid = !cert->isCA;
|
|
if (valid != 1)
|
|
WLOG(WS_LOG_CERTMAN, "cert basic constraint invalid");
|
|
}
|
|
|
|
if (valid) {
|
|
valid =
|
|
WMEMCMP(cert->extAuthKeyId, cert->extSubjKeyId, KEYID_SIZE) != 0;
|
|
if (valid != 1)
|
|
WLOG(WS_LOG_CERTMAN, "cert auth key and subject key mismatch");
|
|
}
|
|
|
|
if (valid) {
|
|
valid =
|
|
((certPolicies[1] != NULL) &&
|
|
(WSTRCMP(certPolicies[1], cert->extCertPolicies[0]) == 0 ||
|
|
WSTRCMP(certPolicies[1], cert->extCertPolicies[1]) == 0)) ||
|
|
((certPolicies[0] != NULL) &&
|
|
(WSTRCMP(certPolicies[0], cert->extCertPolicies[0]) == 0 ||
|
|
WSTRCMP(certPolicies[0], cert->extCertPolicies[1]) == 0));
|
|
if (valid != 1)
|
|
WLOG(WS_LOG_CERTMAN, "cert policy invalid");
|
|
}
|
|
|
|
/* validity period must be utc up to and including 2049, general time
|
|
* after 2049 */
|
|
if (valid) {
|
|
const byte* date;
|
|
int dateSz;
|
|
byte dateFormat;
|
|
struct tm t;
|
|
|
|
dateFormat = cert->afterDate[0]; /* i.e ASN_UTC_TIME */
|
|
dateSz = cert->afterDate[1];
|
|
date = &cert->afterDate[2];
|
|
|
|
wc_GetDateAsCalendarTime(date, dateSz, dateFormat, &t);
|
|
if (t.tm_year <= 149 && dateFormat != ASN_UTC_TIME) {
|
|
WLOG(WS_LOG_CERTMAN, "date format was not utc for year %d",
|
|
t.tm_year);
|
|
valid = 0;
|
|
}
|
|
|
|
if (t.tm_year > 149 && dateFormat != ASN_GENERALIZED_TIME) {
|
|
WLOG(WS_LOG_CERTMAN, "date format was not general for year %d",
|
|
t.tm_year);
|
|
valid = 0;
|
|
}
|
|
}
|
|
|
|
/* encoding of issuer DN must be exact match to CA subject DN */
|
|
if (valid) {
|
|
int sz = min(SIGNER_DIGEST_SIZE, KEYID_SIZE);
|
|
if (XMEMCMP(cert->ca->subjectNameHash, cert->issuerHash, sz) != 0) {
|
|
WLOG(WS_LOG_CERTMAN, "CA subject name hash did not match issuer");
|
|
valid = 0;
|
|
}
|
|
}
|
|
|
|
/* path length must be absent (i.e. 0) */
|
|
if (valid) {
|
|
if (cert->pathLength != 0) {
|
|
WLOG(WS_LOG_CERTMAN, "non-conforming pathlength of %d was larger "
|
|
"than 0", cert->pathLength);
|
|
valid = 0;
|
|
}
|
|
}
|
|
|
|
/* check on FASC-N and UUID */
|
|
if (valid) {
|
|
DNS_entry* current;
|
|
byte hasFascN = 0;
|
|
byte hasUUID = 0;
|
|
byte uuid[DEFAULT_UUID_SZ];
|
|
word32 uuidSz = DEFAULT_UUID_SZ;
|
|
|
|
/* cycle through alt names to check for needed types */
|
|
current = cert->altNames;
|
|
while (current != NULL) {
|
|
#ifdef WOLFSSL_FPKI
|
|
if (current->oidSum == FASCN_OID) {
|
|
hasFascN = 1;
|
|
}
|
|
#endif /* WOLFSSL_FPKI */
|
|
|
|
current = current->next;
|
|
}
|
|
|
|
#ifdef WOLFSSL_FPKI
|
|
if (wc_GetUUIDFromCert(cert, uuid, &uuidSz) == 0) {
|
|
hasUUID = 1;
|
|
}
|
|
#endif /* WOLFSSL_FPKI */
|
|
|
|
/* all must have UUID and worksheet 6 must have FASC-N in addition to
|
|
* UUID */
|
|
if (profile == PROFILE_FPKI_WORKSHEET_6 && hasFascN == 0) {
|
|
WLOG(WS_LOG_CERTMAN, "cert did not inclue a FASC-N");
|
|
valid = 0;
|
|
}
|
|
|
|
if (valid && hasUUID == 0) {
|
|
WLOG(WS_LOG_CERTMAN, "cert did not inclue a UUID");
|
|
valid = 0;
|
|
}
|
|
}
|
|
|
|
if (valid) {
|
|
valid =
|
|
/* Must include all in extKeyUsage */
|
|
((extKeyUsage == 0) ||
|
|
((cert->extExtKeyUsage & extKeyUsage) == extKeyUsage)) &&
|
|
/* Must include all in extKeyUsageSsh */
|
|
((extKeyUsageSsh == 0) ||
|
|
((cert->extExtKeyUsageSsh & extKeyUsageSsh)
|
|
== extKeyUsageSsh));
|
|
if (valid != 1) {
|
|
WLOG(WS_LOG_CERTMAN, "cert did not inclue all ext. key usage");
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_WOLFSSH
|
|
switch (profile) {
|
|
case PROFILE_FPKI_WORKSHEET_6:
|
|
if (valid)
|
|
WLOG(WS_LOG_INFO, "Cert matched FPKI profile 6");
|
|
else
|
|
WLOG(WS_LOG_INFO, "Cert did not match FPKI profile 6");
|
|
break;
|
|
|
|
case PROFILE_FPKI_WORKSHEET_10:
|
|
if (valid)
|
|
WLOG(WS_LOG_INFO, "Cert matched FPKI profile 10");
|
|
else
|
|
WLOG(WS_LOG_INFO, "Cert did not match FPKI profile 10");
|
|
break;
|
|
|
|
case PROFILE_FPKI_WORKSHEET_16:
|
|
if (valid)
|
|
WLOG(WS_LOG_INFO, "Cert matched FPKI profile 16");
|
|
else
|
|
WLOG(WS_LOG_INFO, "Cert did not match FPKI profile 16");
|
|
break;
|
|
}
|
|
#endif /* DEBUG_WOLFSSH */
|
|
|
|
return valid;
|
|
}
|
|
#endif /* WOLFSSH_NO_FPKI */
|
|
|
|
#endif /* WOLFSSH_CERTS */
|