From 618d282d94027db38df2bafb0eb93eba0a958365 Mon Sep 17 00:00:00 2001 From: John Safranek Date: Mon, 28 Apr 2014 11:03:24 -0700 Subject: [PATCH] Decodes the Name Constraints certificate extension on the CA cert and checks the names on the peer cert, rejecting it if invalid based on the name. --- ctaocrypt/src/asn.c | 366 ++++++++++++++++++++++++++++++++- ctaocrypt/src/error.c | 4 + cyassl/ctaocrypt/asn.h | 29 +++ cyassl/ctaocrypt/error-crypt.h | 1 + src/ssl.c | 8 + 5 files changed, 407 insertions(+), 1 deletion(-) diff --git a/ctaocrypt/src/asn.c b/ctaocrypt/src/asn.c index 80c11c0c7..d1ae54c20 100644 --- a/ctaocrypt/src/asn.c +++ b/ctaocrypt/src/asn.c @@ -1272,6 +1272,11 @@ void InitDecodedCert(DecodedCert* cert, byte* source, word32 inSz, void* heap) cert->subjectCNLen = 0; cert->subjectCNStored = 0; cert->altNames = NULL; +#ifndef IGNORE_NAME_CONSTRAINTS + cert->altEmailNames = NULL; + cert->permittedNames = NULL; + cert->excludedNames = NULL; +#endif /* IGNORE_NAME_CONSTRAINTS */ cert->issuer[0] = '\0'; cert->subject[0] = '\0'; cert->source = source; /* don't own */ @@ -1341,6 +1346,9 @@ void InitDecodedCert(DecodedCert* cert, byte* source, word32 inSz, void* heap) cert->extSubjKeyIdSrc = NULL; cert->extSubjKeyIdSz = 0; #endif /* OPENSSL_EXTRA */ +#if defined(OPENSSL_EXTRA) || !defined(IGNORE_NAME_CONSTRAINTS) + cert->extNameConstraintSet = 0; +#endif /* OPENSSL_EXTRA || !IGNORE_NAME_CONSTRAINTS */ #ifdef HAVE_ECC cert->pkCurveOID = 0; #endif /* HAVE_ECC */ @@ -1372,6 +1380,22 @@ void FreeAltNames(DNS_entry* altNames, void* heap) } } +#ifndef IGNORE_NAME_CONSTRAINTS + +void FreeNameSubtrees(Base_entry* names, void* heap) +{ + (void)heap; + + while (names) { + Base_entry* tmp = names->next; + + XFREE(names->name, heap, DYNAMIC_TYPE_ALTNAME); + XFREE(names, heap, DYNAMIC_TYPE_ALTNAME); + names = tmp; + } +} + +#endif /* IGNORE_NAME_CONSTRAINTS */ void FreeDecodedCert(DecodedCert* cert) { @@ -1381,6 +1405,14 @@ void FreeDecodedCert(DecodedCert* cert) XFREE(cert->publicKey, cert->heap, DYNAMIC_TYPE_PUBLIC_KEY); if (cert->altNames) FreeAltNames(cert->altNames, cert->heap); +#ifndef IGNORE_NAME_CONSTRAINTS + if (cert->altEmailNames) + FreeAltNames(cert->altEmailNames, cert->heap); + if (cert->permittedNames) + FreeNameSubtrees(cert->permittedNames, cert->heap); + if (cert->excludedNames) + FreeNameSubtrees(cert->excludedNames, cert->heap); +#endif /* IGNORE_NAME_CONSTRAINTS */ #ifdef CYASSL_SEP XFREE(cert->deviceType, cert->heap, 0); XFREE(cert->hwType, cert->heap, 0); @@ -1863,7 +1895,30 @@ static int GetName(DecodedCert* cert, int nameType) dName->emailIdx = cert->srcIdx; dName->emailLen = adv; #endif /* OPENSSL_EXTRA */ + #ifndef IGNORE_NAME_CONSTRAINTS + { + DNS_entry* emailName = NULL; + emailName = (DNS_entry*)XMALLOC(sizeof(DNS_entry), + cert->heap, DYNAMIC_TYPE_ALTNAME); + if (emailName == NULL) { + CYASSL_MSG("\tOut of Memory"); + return MEMORY_E; + } + emailName->name = (char*)XMALLOC(adv + 1, + cert->heap, DYNAMIC_TYPE_ALTNAME); + if (emailName->name == NULL) { + CYASSL_MSG("\tOut of Memory"); + return MEMORY_E; + } + XMEMCPY(emailName->name, + &cert->source[cert->srcIdx], adv); + emailName->name[adv] = 0; + + emailName->next = cert->altEmailNames; + cert->altEmailNames = emailName; + } + #endif /* IGNORE_NAME_CONSTRAINTS */ if (!tooBig) { XMEMCPY(&full[idx], &cert->source[cert->srcIdx], adv); idx += adv; @@ -2870,6 +2925,152 @@ static int ConfirmSignature(const byte* buf, word32 bufSz, } +#ifndef IGNORE_NAME_CONSTRAINTS + +static int MatchBaseName(int type, const char* name, int nameSz, + const char* base, int baseSz) +{ + if (base == NULL || baseSz <= 0 || name == NULL || nameSz <= 0 || + name[0] == '.' || nameSz < baseSz || + (type != ASN_RFC822_TYPE && type != ASN_DNS_TYPE)) + return 0; + + /* If an email type, handle special cases where the base is only + * a domain, or is an email address itself. */ + if (type == ASN_RFC822_TYPE) { + const char* p = NULL; + int count = 0; + + if (base[0] != '.') { + p = base; + count = 0; + + /* find the '@' in the base */ + while (*p != '@' && count < baseSz) { + count++; + p++; + } + + /* No '@' in base, reset p to NULL */ + if (count >= baseSz) + p = NULL; + } + + if (p == NULL) { + /* Base isn't an email address, it is a domain name, + * wind the name forward one character past its '@'. */ + p = name; + count = 0; + while (*p != '@' && count < baseSz) { + count++; + p++; + } + + if (count < baseSz && *p == '@') { + name = p + 1; + nameSz -= count + 1; + } + } + } + + if ((type == ASN_DNS_TYPE || type == ASN_RFC822_TYPE) && base[0] == '.') { + int szAdjust = nameSz - baseSz; + name += szAdjust; + nameSz -= szAdjust; + } + + while (nameSz > 0) { + if (XTOLOWER(*name++) != XTOLOWER(*base++)) + return 0; + nameSz--; + } + + return 1; +} + + +static int ConfirmNameConstraints(Signer* signer, DecodedCert* cert) +{ + if (signer == NULL || cert == NULL) + return 0; + + /* Check against the excluded list */ + if (signer->excludedNames) { + Base_entry* base = signer->excludedNames; + + while (base != NULL) { + if (base->type == ASN_DNS_TYPE) { + DNS_entry* name = cert->altNames; + while (name != NULL) { + if (MatchBaseName(ASN_DNS_TYPE, + name->name, (int)XSTRLEN(name->name), + base->name, (int)XSTRLEN(base->name))) + return 0; + name = name->next; + } + } + else if (base->type == ASN_RFC822_TYPE) { + DNS_entry* name = cert->altEmailNames; + while (name != NULL) { + if (MatchBaseName(ASN_RFC822_TYPE, + name->name, (int)XSTRLEN(name->name), + base->name, (int)XSTRLEN(base->name))) + return 0; + name = name->next; + } + } + base = base->next; + } + } + + /* Check against the permitted list */ + if (signer->permittedNames != NULL) { + int needDns = 0; + int matchDns = 0; + int needEmail = 0; + int matchEmail = 0; + Base_entry* base = signer->permittedNames; + + while (base != NULL) { + if (base->type == ASN_DNS_TYPE) { + DNS_entry* name = cert->altNames; + + if (name != NULL) + needDns = 1; + + while (name != NULL) { + matchDns = MatchBaseName(ASN_DNS_TYPE, + name->name, (int)XSTRLEN(name->name), + base->name, (int)XSTRLEN(base->name)); + name = name->next; + } + } + else if (base->type == ASN_RFC822_TYPE) { + DNS_entry* name = cert->altEmailNames; + + if (name != NULL) + needEmail = 1; + + while (name != NULL) { + matchEmail = MatchBaseName(ASN_DNS_TYPE, + name->name, (int)XSTRLEN(name->name), + base->name, (int)XSTRLEN(base->name)); + name = name->next; + } + } + base = base->next; + } + + if ((needDns && !matchDns) || (needEmail && !matchEmail)) + return 0; + } + + return 1; +} + +#endif /* IGNORE_NAME_CONSTRAINTS */ + + static int DecodeAltNames(byte* input, int sz, DecodedCert* cert) { word32 idx = 0; @@ -2924,6 +3125,43 @@ static int DecodeAltNames(byte* input, int sz, DecodedCert* cert) length -= strLen; idx += strLen; } +#ifndef IGNORE_NAME_CONSTRAINTS + else if (b == (ASN_CONTEXT_SPECIFIC | ASN_RFC822_TYPE)) { + DNS_entry* emailEntry; + int strLen; + word32 lenStartIdx = idx; + + if (GetLength(input, &idx, &strLen, sz) < 0) { + CYASSL_MSG("\tfail: str length"); + return ASN_PARSE_E; + } + length -= (idx - lenStartIdx); + + emailEntry = (DNS_entry*)XMALLOC(sizeof(DNS_entry), cert->heap, + DYNAMIC_TYPE_ALTNAME); + if (emailEntry == NULL) { + CYASSL_MSG("\tOut of Memory"); + return ASN_PARSE_E; + } + + emailEntry->name = (char*)XMALLOC(strLen + 1, cert->heap, + DYNAMIC_TYPE_ALTNAME); + if (emailEntry->name == NULL) { + CYASSL_MSG("\tOut of Memory"); + XFREE(emailEntry, cert->heap, DYNAMIC_TYPE_ALTNAME); + return ASN_PARSE_E; + } + + XMEMCPY(emailEntry->name, &input[idx], strLen); + emailEntry->name[strLen] = '\0'; + + emailEntry->next = cert->altEmailNames; + cert->altEmailNames = emailEntry; + + length -= strLen; + idx += strLen; + } +#endif /* IGNORE_NAME_CONSTRAINTS */ #ifdef CYASSL_SEP else if (b == (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | ASN_OTHER_TYPE)) { @@ -3329,7 +3567,7 @@ static int DecodeExtKeyUsage(byte* input, int sz, DecodedCert* cert) CYASSL_ENTER("DecodeExtKeyUsage"); if (GetSequence(input, &idx, &length, sz) < 0) { - CYASSL_MSG("\tfail: should be a SEQUENCE\n"); + CYASSL_MSG("\tfail: should be a SEQUENCE"); return ASN_PARSE_E; } @@ -3366,6 +3604,103 @@ static int DecodeExtKeyUsage(byte* input, int sz, DecodedCert* cert) } +#ifndef IGNORE_NAME_CONSTRAINTS +static int DecodeSubtree(byte* input, int sz, Base_entry** head, void* heap) +{ + word32 idx = 0; + + (void)heap; + + while (idx < (word32)sz) { + int seqLength, strLength; + word32 nameIdx; + byte b; + + if (GetSequence(input, &idx, &seqLength, sz) < 0) { + CYASSL_MSG("\tfail: should be a SEQUENCE"); + return ASN_PARSE_E; + } + + nameIdx = idx; + b = input[nameIdx++]; + if (GetLength(input, &nameIdx, &strLength, sz) <= 0) { + CYASSL_MSG("\tinvalid length"); + return ASN_PARSE_E; + } + + if (b == (ASN_CONTEXT_SPECIFIC | ASN_DNS_TYPE) || + b == (ASN_CONTEXT_SPECIFIC | ASN_RFC822_TYPE)) { + + Base_entry* entry = (Base_entry*)XMALLOC(sizeof(Base_entry), + heap, DYNAMIC_TYPE_ALTNAME); + + if (entry == NULL) { + CYASSL_MSG("allocate error"); + return MEMORY_E; + } + + entry->name = (char*)XMALLOC(strLength + 1, + heap, DYNAMIC_TYPE_ALTNAME); + if (entry->name == NULL) { + CYASSL_MSG("allocate error"); + return MEMORY_E; + } + + XMEMCPY(entry->name, &input[nameIdx], strLength); + entry->name[strLength] = '\0'; + entry->type = b & 0x0F; + + entry->next = *head; + *head = entry; + } + + idx += seqLength; + } + + return 0; +} + + +static int DecodeNameConstraints(byte* input, int sz, DecodedCert* cert) +{ + word32 idx = 0; + int length = 0; + + CYASSL_ENTER("DecodeNameConstraints"); + + if (GetSequence(input, &idx, &length, sz) < 0) { + CYASSL_MSG("\tfail: should be a SEQUENCE"); + return ASN_PARSE_E; + } + + while (idx < (word32)sz) { + byte b = input[idx++]; + Base_entry** subtree = NULL; + + if (GetLength(input, &idx, &length, sz) <= 0) { + CYASSL_MSG("\tinvalid length"); + return ASN_PARSE_E; + } + + if (b == (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | 0)) + subtree = &cert->permittedNames; + else if (b == (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | 1)) + subtree = &cert->excludedNames; + else { + CYASSL_MSG("\tinvalid subtree"); + return ASN_PARSE_E; + } + + DecodeSubtree(input + idx, length, subtree, cert->heap); + + idx += length; + } + + return 0; +} +#endif /* IGNORE_NAME_CONSTRAINTS */ + + #ifdef CYASSL_SEP static int DecodeCertPolicy(byte* input, int sz, DecodedCert* cert) { @@ -3552,6 +3887,17 @@ static int DecodeCertExtensions(DecodedCert* cert) return ASN_PARSE_E; break; + #ifndef IGNORE_NAME_CONSTRAINTS + case NAME_CONS_OID: + cert->extNameConstraintSet = 1; + #ifdef OPENSSL_EXTRA + cert->extNameConstraintCrit = critical; + #endif + if (DecodeNameConstraints(&input[idx], length, cert) < 0) + return ASN_PARSE_E; + break; + #endif /* IGNORE_NAME_CONSTRAINTS */ + case INHIBIT_ANY_OID: CYASSL_MSG("Inhibit anyPolicy extension not supported yet."); break; @@ -3714,6 +4060,14 @@ int ParseCertRelative(DecodedCert* cert, int type, int verify, void* cm) CYASSL_MSG("Confirm signature failed"); return ASN_SIG_CONFIRM_E; } +#ifndef IGNORE_NAME_CONSTRAINTS + /* check that this cert's name is permitted by the signer's + * name constraints */ + if (!ConfirmNameConstraints(ca, cert)) { + CYASSL_MSG("Confirm name constraint failed"); + return ASN_NAME_INVALID_E; + } +#endif /* IGNORE_NAME_CONSTRAINTS */ } else { /* no signer */ @@ -3743,6 +4097,10 @@ Signer* MakeSigner(void* heap) signer->publicKey = NULL; signer->nameLen = 0; signer->name = NULL; + #ifndef IGNORE_NAME_CONSTRAINTS + signer->permittedNames = NULL; + signer->excludedNames = NULL; + #endif /* IGNORE_NAME_CONSTRAINTS */ signer->next = NULL; } (void)heap; @@ -3756,6 +4114,12 @@ void FreeSigner(Signer* signer, void* heap) { XFREE(signer->name, heap, DYNAMIC_TYPE_SUBJECT_CN); XFREE(signer->publicKey, heap, DYNAMIC_TYPE_PUBLIC_KEY); + #ifndef IGNORE_NAME_CONSTRAINTS + if (signer->permittedNames) + FreeNameSubtrees(signer->permittedNames, heap); + if (signer->excludedNames) + FreeNameSubtrees(signer->excludedNames, heap); + #endif XFREE(signer, heap, DYNAMIC_TYPE_SIGNER); (void)heap; diff --git a/ctaocrypt/src/error.c b/ctaocrypt/src/error.c index e86a0071e..3b629ae08 100644 --- a/ctaocrypt/src/error.c +++ b/ctaocrypt/src/error.c @@ -351,6 +351,10 @@ void CTaoCryptErrorString(int error, char* buffer) XSTRNCPY(buffer, "FIPS mode not allowed error", max); break; + case ASN_NAME_INVALID_E: + XSTRNCPY(buffer, "Name Constraint error", max); + break; + default: XSTRNCPY(buffer, "unknown error number", max); diff --git a/cyassl/ctaocrypt/asn.h b/cyassl/ctaocrypt/asn.h index 11108d3f8..9aa3d04ff 100644 --- a/cyassl/ctaocrypt/asn.h +++ b/cyassl/ctaocrypt/asn.h @@ -64,6 +64,7 @@ enum ASN_Tags { ASN_SET = 0x11, ASN_UTC_TIME = 0x17, ASN_OTHER_TYPE = 0x00, + ASN_RFC822_TYPE = 0x01, ASN_DNS_TYPE = 0x02, ASN_GENERALIZED_TIME = 0x18, CRL_EXTENSIONS = 0xa0, @@ -219,6 +220,7 @@ enum Extensions_Sum { KEY_USAGE_OID = 129, /* 2.5.29.15 */ INHIBIT_ANY_OID = 168, /* 2.5.29.54 */ EXT_KEY_USAGE_OID = 151, /* 2.5.29.37 */ + NAME_CONS_OID = 144 /* 2.5.29.30 */ }; enum CertificatePolicy_Sum { @@ -272,6 +274,15 @@ struct DNS_entry { }; +typedef struct Base_entry Base_entry; + +struct Base_entry { + Base_entry* next; /* next on name base list */ + char* name; /* actual name base */ + byte type; /* Name base type (DNS or RFC822) */ +}; + + struct DecodedName { char* fullName; int fullNameLen; @@ -315,6 +326,11 @@ struct DecodedCert { word32 keyOID; /* sum of key algo object id */ int version; /* cert version, 1 or 3 */ DNS_entry* altNames; /* alt names list of dns entries */ +#ifndef IGNORE_NAME_CONSTRAINTS + DNS_entry* altEmailNames; /* alt names list of RFC822 entries */ + Base_entry* permittedNames; /* Permitted name bases */ + Base_entry* excludedNames; /* Excluded name bases */ +#endif /* IGNORE_NAME_CONSTRAINTS */ byte subjectHash[SHA_SIZE]; /* hash of all Names */ byte issuerHash[SHA_SIZE]; /* hash of all Names */ #ifdef HAVE_OCSP @@ -344,6 +360,9 @@ struct DecodedCert { byte extSubjKeyIdSet; /* Set when the SKID was read from cert */ byte extAuthKeyId[SHA_SIZE]; /* Authority Key ID */ byte extAuthKeyIdSet; /* Set when the AKID was read from cert */ +#ifndef IGNORE_NAME_CONSTRAINTS + byte extNameConstraintSet; +#endif /* IGNORE_NAME_CONSTRAINTS */ byte isCA; /* CA basic constraint true */ byte extKeyUsageSet; word16 extKeyUsage; /* Key usage bitfield */ @@ -357,6 +376,9 @@ struct DecodedCert { byte extSubjAltNameSet; byte extSubjAltNameCrit; byte extAuthKeyIdCrit; +#ifndef IGNORE_NAME_CONSTRAINTS + byte extNameConstraintCrit; +#endif /* IGNORE_NAME_CONSTRAINTS */ byte extSubjKeyIdCrit; byte extKeyUsageCrit; byte extExtKeyUsageCrit; @@ -430,6 +452,10 @@ struct Signer { byte* publicKey; int nameLen; char* name; /* common name */ +#ifndef IGNORE_NAME_CONSTRAINTS + Base_entry* permittedNames; + Base_entry* excludedNames; +#endif /* IGNORE_NAME_CONSTRAINTS */ byte subjectNameHash[SIGNER_DIGEST_SIZE]; /* sha hash of names in certificate */ #ifndef NO_SKID @@ -448,6 +474,9 @@ struct Signer { #endif CYASSL_TEST_API void FreeAltNames(DNS_entry*, void*); +#ifndef IGNORE_NAME_CONSTRAINTS + CYASSL_TEST_API void FreeNameSubtrees(Base_entry*, void*); +#endif /* IGNORE_NAME_CONSTRAINTS */ CYASSL_TEST_API void InitDecodedCert(DecodedCert*, byte*, word32, void*); CYASSL_TEST_API void FreeDecodedCert(DecodedCert*); CYASSL_TEST_API int ParseCert(DecodedCert*, int type, int verify, void* cm); diff --git a/cyassl/ctaocrypt/error-crypt.h b/cyassl/ctaocrypt/error-crypt.h index acd2dbc8c..113d2d73a 100644 --- a/cyassl/ctaocrypt/error-crypt.h +++ b/cyassl/ctaocrypt/error-crypt.h @@ -123,6 +123,7 @@ enum { PKCS7_OID_E = -195, /* PKCS#7, mismatched OID error */ PKCS7_RECIP_E = -196, /* PKCS#7, recipient error */ FIPS_NOT_ALLOWED_E = -197, /* FIPS not allowed error */ + ASN_NAME_INVALID_E = -198, /* ASN name constraint error */ MIN_CODE_E = -200 /* errors -101 - -199 */ }; diff --git a/src/ssl.c b/src/ssl.c index f915d81fe..bd54d7fda 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -1520,6 +1520,10 @@ int AddCA(CYASSL_CERT_MANAGER* cm, buffer der, int type, int verify) signer->pubKeySize = cert.pubKeySize; signer->nameLen = cert.subjectCNLen; signer->name = cert.subjectCN; + #ifndef IGNORE_NAME_CONSTRAINTS + signer->permittedNames = cert.permittedNames; + signer->excludedNames = cert.excludedNames; + #endif #ifndef NO_SKID XMEMCPY(signer->subjectKeyIdHash, cert.extSubjKeyId, SHA_DIGEST_SIZE); @@ -1531,6 +1535,10 @@ int AddCA(CYASSL_CERT_MANAGER* cm, buffer der, int type, int verify) cert.publicKey = 0; /* don't free here */ cert.subjectCN = 0; + #ifndef IGNORE_NAME_CONSTRAINTS + cert.permittedNames = NULL; + cert.excludedNames = NULL; + #endif #ifndef NO_SKID row = HashSigner(signer->subjectKeyIdHash);