diff --git a/dlls/crypt32/encode.c b/dlls/crypt32/encode.c index 0c2f2c03afa..ac4f177236c 100644 --- a/dlls/crypt32/encode.c +++ b/dlls/crypt32/encode.c @@ -18,6 +18,7 @@ */ #include #include +#include #include "windef.h" #include "winbase.h" #include "wincrypt.h" @@ -26,8 +27,12 @@ #include "wine/debug.h" /* a few asn.1 tags we need */ -#define ASN_UTCTIME (ASN_UNIVERSAL | ASN_PRIMITIVE | 0x17) -#define ASN_GENERALTIME (ASN_UNIVERSAL | ASN_PRIMITIVE | 0x18) +#define ASN_SETOF (ASN_UNIVERSAL | ASN_PRIMITIVE | 0x11) +#define ASN_NUMERICSTRING (ASN_UNIVERSAL | ASN_PRIMITIVE | 0x12) +#define ASN_PRINTABLESTRING (ASN_UNIVERSAL | ASN_PRIMITIVE | 0x13) +#define ASN_IA5STRING (ASN_UNIVERSAL | ASN_PRIMITIVE | 0x16) +#define ASN_UTCTIME (ASN_UNIVERSAL | ASN_PRIMITIVE | 0x17) +#define ASN_GENERALTIME (ASN_UNIVERSAL | ASN_PRIMITIVE | 0x18) WINE_DEFAULT_DEBUG_CHANNEL(crypt); @@ -336,6 +341,8 @@ static BOOL CRYPT_EncodeEnsureSpace(DWORD dwFlags, *(BYTE **)pbEncoded = LocalAlloc(0, bytesNeeded); if (!*(BYTE **)pbEncoded) ret = FALSE; + else + *pcbEncoded = bytesNeeded; } else if (bytesNeeded > *pcbEncoded) { @@ -346,6 +353,358 @@ static BOOL CRYPT_EncodeEnsureSpace(DWORD dwFlags, return ret; } +static BOOL WINAPI CRYPT_AsnEncodeOid(DWORD dwCertEncodingType, + LPCSTR pszObjId, BYTE *pbEncoded, DWORD *pcbEncoded) +{ + DWORD bytesNeeded = 2; + BOOL ret = TRUE; + int firstPos = 0; + BYTE firstByte = 0; + + if (pszObjId) + { + const char *ptr; + int val1, val2; + + if (sscanf(pszObjId, "%d.%d.%n", &val1, &val2, &firstPos) != 2) + { + SetLastError(CRYPT_E_ASN1_ERROR); + return FALSE; + } + bytesNeeded++; + firstByte = val1 * 40 + val2; + ptr = pszObjId + firstPos; + while (ret && *ptr) + { + int pos; + + /* note I assume each component is at most 32-bits long in base 2 */ + if (sscanf(ptr, "%d%n", &val1, &pos) == 1) + { + if (val1 >= 0x10000000) + bytesNeeded += 5; + else if (val1 >= 0x200000) + bytesNeeded += 4; + else if (val1 >= 0x4000) + bytesNeeded += 3; + else if (val1 >= 0x80) + bytesNeeded += 2; + else + bytesNeeded += 1; + ptr += pos; + if (*ptr == '.') + ptr++; + } + else + { + SetLastError(CRYPT_E_ASN1_ERROR); + return FALSE; + } + } + } + if (pbEncoded) + { + if (*pbEncoded < bytesNeeded) + { + SetLastError(ERROR_MORE_DATA); + ret = FALSE; + } + else + { + *pbEncoded++ = ASN_OBJECTIDENTIFIER; + *pbEncoded++ = bytesNeeded - 2; + if (pszObjId) + { + const char *ptr; + int val, pos; + + *pbEncoded++ = firstByte; + ptr = pszObjId + firstPos; + while (ret && *ptr) + { + sscanf(ptr, "%d%n", &val, &pos); + { + unsigned char outBytes[5]; + int numBytes, i; + + if (val >= 0x10000000) + numBytes = 5; + else if (val >= 0x200000) + numBytes = 4; + else if (val >= 0x4000) + numBytes = 3; + else if (val >= 0x80) + numBytes = 2; + else + numBytes = 1; + for (i = numBytes; i > 0; i--) + { + outBytes[i - 1] = val & 0x7f; + val >>= 7; + } + for (i = 0; i < numBytes - 1; i++) + *pbEncoded++ = outBytes[i] | 0x80; + *pbEncoded++ = outBytes[i]; + ptr += pos; + if (*ptr == '.') + ptr++; + } + } + } + } + } + *pcbEncoded = bytesNeeded; + return ret; +} + +static BOOL WINAPI CRYPT_AsnEncodeNameValue(DWORD dwCertEncodingType, + CERT_NAME_VALUE *value, BYTE *pbEncoded, DWORD *pcbEncoded) +{ + BYTE tag; + DWORD bytesNeeded; + BOOL ret = TRUE; + + switch (value->dwValueType) + { + case CERT_RDN_NUMERIC_STRING: + tag = ASN_NUMERICSTRING; + bytesNeeded = 2 + value->Value.cbData; + break; + case CERT_RDN_PRINTABLE_STRING: + tag = ASN_PRINTABLESTRING; + bytesNeeded = 2 + value->Value.cbData; + break; + case CERT_RDN_IA5_STRING: + tag = ASN_IA5STRING; + bytesNeeded = 2 + value->Value.cbData; + break; + case CERT_RDN_ANY_TYPE: + /* explicitly disallowed */ + SetLastError(HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER)); + return FALSE; + default: + FIXME("String type %ld unimplemented\n", value->dwValueType); + return FALSE; + } + if (pbEncoded) + { + if (*pcbEncoded < bytesNeeded) + { + SetLastError(ERROR_MORE_DATA); + ret = FALSE; + } + else + { + *pbEncoded++ = tag; + *pbEncoded++ = bytesNeeded - 2; + switch (value->dwValueType) + { + case CERT_RDN_NUMERIC_STRING: + case CERT_RDN_PRINTABLE_STRING: + case CERT_RDN_IA5_STRING: + memcpy(pbEncoded, value->Value.pbData, value->Value.cbData); + } + } + } + *pcbEncoded = bytesNeeded; + return ret; +} + +static BOOL WINAPI CRYPT_AsnEncodeRdnAttr(DWORD dwCertEncodingType, + CERT_RDN_ATTR *attr, BYTE *pbEncoded, DWORD *pcbEncoded) +{ + DWORD bytesNeeded, size; + BOOL ret; + + bytesNeeded = 2; /* tag and len */ + ret = CRYPT_AsnEncodeOid(dwCertEncodingType, attr->pszObjId, NULL, &size); + if (ret) + { + bytesNeeded += size; + /* hack: a CERT_RDN_ATTR is identical to a CERT_NAME_VALUE beginning + * with dwValueType, so "cast" it to get its encoded size + */ + ret = CRYPT_AsnEncodeNameValue(dwCertEncodingType, + (CERT_NAME_VALUE *)&attr->dwValueType, NULL, &size); + if (ret) + { + bytesNeeded += size; + if (pbEncoded) + { + if (*pcbEncoded < bytesNeeded) + { + SetLastError(ERROR_MORE_DATA); + ret = FALSE; + } + else + { + *pbEncoded++ = ASN_CONSTRUCTOR | ASN_SEQUENCE; + *pbEncoded++ = bytesNeeded - 2; + size = bytesNeeded - 2; + ret = CRYPT_AsnEncodeOid(dwCertEncodingType, attr->pszObjId, + pbEncoded, &size); + if (ret) + { + pbEncoded += size; + size = bytesNeeded - 2 - size; + ret = CRYPT_AsnEncodeNameValue(dwCertEncodingType, + (CERT_NAME_VALUE *)&attr->dwValueType, pbEncoded, + &size); + } + } + } + *pcbEncoded = bytesNeeded; + } + } + return ret; +} + +static int BLOBComp(const void *l, const void *r) +{ + CRYPT_DER_BLOB *a = (CRYPT_DER_BLOB *)l, *b = (CRYPT_DER_BLOB *)r; + int ret; + + if (!(ret = memcmp(a->pbData, b->pbData, min(a->cbData, b->cbData)))) + ret = a->cbData - b->cbData; + return ret; +} + +/* This encodes as a SET OF, which in DER must be lexicographically sorted. + */ +static BOOL WINAPI CRYPT_AsnEncodeRdn(DWORD dwCertEncodingType, CERT_RDN *rdn, + BYTE *pbEncoded, DWORD *pcbEncoded) +{ + DWORD bytesNeeded, i; + BOOL ret; + CRYPT_DER_BLOB *blobs = NULL; + + ret = TRUE; + if (rdn->cRDNAttr) + { + if (!rdn->rgRDNAttr) + { + SetLastError(STATUS_ACCESS_VIOLATION); + ret = FALSE; + } + else + { + blobs = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, + rdn->cRDNAttr * sizeof(CRYPT_DER_BLOB)); + if (!blobs) + ret = FALSE; + } + } + bytesNeeded = 2; /* tag and len */ + for (i = 0; ret && i < rdn->cRDNAttr; i++) + { + ret = CRYPT_AsnEncodeRdnAttr(dwCertEncodingType, &rdn->rgRDNAttr[i], + NULL, &blobs[i].cbData); + if (ret) + bytesNeeded += blobs[i].cbData; + } + if (ret) + { + if (pbEncoded) + { + if (*pcbEncoded < bytesNeeded) + { + SetLastError(ERROR_MORE_DATA); + ret = FALSE; + } + else + { + for (i = 0; ret && i < rdn->cRDNAttr; i++) + { + blobs[i].pbData = HeapAlloc(GetProcessHeap(), 0, + blobs[i].cbData); + if (!blobs[i].pbData) + ret = FALSE; + else + ret = CRYPT_AsnEncodeRdnAttr(dwCertEncodingType, + &rdn->rgRDNAttr[i], blobs[i].pbData, &blobs[i].cbData); + } + if (ret) + { + qsort(blobs, rdn->cRDNAttr, sizeof(CRYPT_DER_BLOB), + BLOBComp); + *pbEncoded++ = ASN_CONSTRUCTOR | ASN_SETOF; + *pbEncoded++ = (BYTE)bytesNeeded - 2; + for (i = 0; ret && i < rdn->cRDNAttr; i++) + { + memcpy(pbEncoded, blobs[i].pbData, blobs[i].cbData); + pbEncoded += blobs[i].cbData; + } + } + } + } + *pcbEncoded = bytesNeeded; + } + if (blobs) + { + for (i = 0; i < rdn->cRDNAttr; i++) + HeapFree(GetProcessHeap(), 0, blobs[i].pbData); + HeapFree(GetProcessHeap(), 0, blobs); + } + return ret; +} + +static BOOL WINAPI CRYPT_AsnEncodeName(DWORD dwCertEncodingType, + LPCSTR lpszStructType, const void *pvStructInfo, DWORD dwFlags, + PCRYPT_ENCODE_PARA pEncodePara, BYTE *pbEncoded, DWORD *pcbEncoded) +{ + CERT_NAME_INFO *info = (CERT_NAME_INFO *)pvStructInfo; + DWORD bytesNeeded, size, i; + BOOL ret; + + if (!pvStructInfo) + { + SetLastError(STATUS_ACCESS_VIOLATION); + return FALSE; + } + if (info->cRDN && !info->rgRDN) + { + SetLastError(STATUS_ACCESS_VIOLATION); + return FALSE; + } + TRACE("encoding name with %ld RDNs\n", info->cRDN); + bytesNeeded = 2; /* tag and len */ + ret = TRUE; + for (i = 0; ret && i < info->cRDN; i++) + { + ret = CRYPT_AsnEncodeRdn(dwCertEncodingType, &info->rgRDN[i], NULL, + &size); + if (ret) + bytesNeeded += size; + } + if (ret) + { + if (!pbEncoded) + { + *pcbEncoded = bytesNeeded; + return TRUE; + } + if (!CRYPT_EncodeEnsureSpace(dwFlags, pEncodePara, pbEncoded, + pcbEncoded, bytesNeeded)) + return FALSE; + if (dwFlags & CRYPT_ENCODE_ALLOC_FLAG) + pbEncoded = *(BYTE **)pbEncoded; + *pbEncoded++ = ASN_CONSTRUCTOR | ASN_SEQUENCE; + *pbEncoded++ = (BYTE)bytesNeeded - 2; + for (i = 0; ret && i < info->cRDN; i++) + { + size = bytesNeeded; + ret = CRYPT_AsnEncodeRdn(dwCertEncodingType, &info->rgRDN[i], + pbEncoded, &size); + if (ret) + { + pbEncoded += size; + bytesNeeded -= size; + } + } + } + return ret; +} + static BOOL WINAPI CRYPT_AsnEncodeInt(DWORD dwCertEncodingType, LPCSTR lpszStructType, const void *pvStructInfo, DWORD dwFlags, PCRYPT_ENCODE_PARA pEncodePara, BYTE *pbEncoded, DWORD *pcbEncoded) @@ -356,7 +715,7 @@ static BOOL WINAPI CRYPT_AsnEncodeInt(DWORD dwCertEncodingType, if (!pvStructInfo) { - SetLastError(ERROR_INVALID_PARAMETER); + SetLastError(STATUS_ACCESS_VIOLATION); return FALSE; } @@ -425,7 +784,7 @@ static BOOL WINAPI CRYPT_AsnEncodeUtcTime(DWORD dwCertEncodingType, if (!pvStructInfo) { - SetLastError(ERROR_INVALID_PARAMETER); + SetLastError(STATUS_ACCESS_VIOLATION); return FALSE; } /* Sanity check the year, this is a two-digit year format */ @@ -469,7 +828,7 @@ static BOOL WINAPI CRYPT_AsnEncodeGeneralizedTime(DWORD dwCertEncodingType, if (!pvStructInfo) { - SetLastError(ERROR_INVALID_PARAMETER); + SetLastError(STATUS_ACCESS_VIOLATION); return FALSE; } if (!pbEncoded) @@ -502,7 +861,7 @@ static BOOL WINAPI CRYPT_AsnEncodeChoiceOfTime(DWORD dwCertEncodingType, if (!pvStructInfo) { - SetLastError(ERROR_INVALID_PARAMETER); + SetLastError(STATUS_ACCESS_VIOLATION); return FALSE; } /* Check the year, if it's in the UTCTime range call that encode func */ @@ -551,6 +910,9 @@ BOOL WINAPI CryptEncodeObjectEx(DWORD dwCertEncodingType, LPCSTR lpszStructType, { switch (LOWORD(lpszStructType)) { + case (WORD)X509_NAME: + encodeFunc = CRYPT_AsnEncodeName; + break; case (WORD)X509_INTEGER: encodeFunc = CRYPT_AsnEncodeInt; break; @@ -639,6 +1001,8 @@ static BOOL CRYPT_DecodeEnsureSpace(DWORD dwFlags, *(BYTE **)pvStructInfo = LocalAlloc(0, bytesNeeded); if (!*(BYTE **)pvStructInfo) ret = FALSE; + else + *pcbStructInfo = bytesNeeded; } else if (*pcbStructInfo < bytesNeeded) { @@ -649,6 +1013,468 @@ static BOOL CRYPT_DecodeEnsureSpace(DWORD dwFlags, return ret; } +/* FIXME: honor the CRYPT_DECODE_SHARE_OID_FLAG. */ +static BOOL WINAPI CRYPT_AsnDecodeOid(DWORD dwCertEncodingType, + const BYTE *pbEncoded, DWORD cbEncoded, DWORD dwFlags, LPSTR pszObjId, + DWORD *pcbObjId) +{ + BOOL ret = TRUE; + DWORD bytesNeeded; + + /* cbEncoded is an upper bound on the number of bytes, not the actual + * count: check the count for sanity. + */ + if (cbEncoded <= 1 || pbEncoded[1] > cbEncoded - 2) + { + SetLastError(CRYPT_E_ASN1_EOD); + return FALSE; + } + if (pbEncoded[0] != ASN_OBJECTIDENTIFIER) + { + SetLastError(CRYPT_E_ASN1_BADTAG); + return FALSE; + } + if (pbEncoded[1]) + { + /* The largest possible string for the first two components is 2.175 + * (= 2 * 40 + 175 = 255), so this is big enough. + */ + char firstTwo[6]; + const BYTE *ptr; + + snprintf(firstTwo, sizeof(firstTwo), "%d.%d", pbEncoded[2] / 40, + pbEncoded[2] - (pbEncoded[2] / 40) * 40); + bytesNeeded = strlen(firstTwo) + 1; + for (ptr = pbEncoded + 3; ret && ptr - pbEncoded - 2 < pbEncoded[1]; ) + { + /* large enough for ".4000000" */ + char str[9]; + int val = 0; + + while (ptr - pbEncoded - 2 < pbEncoded[1] && (*ptr & 0x80)) + { + val <<= 7; + val |= *ptr & 0x7f; + ptr++; + } + if (ptr - pbEncoded - 2 >= pbEncoded[1] || (*ptr & 0x80)) + { + SetLastError(CRYPT_E_ASN1_CORRUPT); + ret = FALSE; + } + else + { + val <<= 7; + val |= *ptr++; + snprintf(str, sizeof(str), ".%d", val); + bytesNeeded += strlen(str); + } + } + if (!pszObjId) + *pcbObjId = bytesNeeded; + else if (*pcbObjId < bytesNeeded) + { + *pcbObjId = bytesNeeded; + SetLastError(ERROR_MORE_DATA); + ret = FALSE; + } + else + { + sprintf(pszObjId, "%d.%d", pbEncoded[2] / 40, + pbEncoded[2] - (pbEncoded[2] / 40) * 40); + pszObjId += strlen(pszObjId); + for (ptr = pbEncoded + 3; ret && ptr - pbEncoded - 2 < pbEncoded[1]; + ) + { + int val = 0; + + while (ptr - pbEncoded - 2 < pbEncoded[1] && (*ptr & 0x80)) + { + val <<= 7; + val |= *ptr & 0x7f; + ptr++; + } + val <<= 7; + val |= *ptr++; + sprintf(pszObjId, ".%d", val); + pszObjId += strlen(pszObjId); + } + } + } + else + bytesNeeded = 0; + *pcbObjId = bytesNeeded; + return ret; +} + +/* Warning: this assumes the address of value->Value.pbData is already set, in + * order to avoid overwriting memory. (In some cases, it may change it, if it + * doesn't copy anything to memory.) Be sure to set it correctly! + */ +static BOOL WINAPI CRYPT_AsnDecodeNameValue(DWORD dwCertEncodingType, + const BYTE *pbEncoded, DWORD cbEncoded, DWORD dwFlags, CERT_NAME_VALUE *value, + DWORD *pcbValue) +{ + DWORD bytesNeeded; + BOOL ret = TRUE; + + /* cbEncoded is an upper bound on the number of bytes, not the actual + * count: check the count for sanity. + */ + if (cbEncoded <= 1 || pbEncoded[1] > cbEncoded - 2) + { + SetLastError(CRYPT_E_ASN1_EOD); + return FALSE; + } + switch (pbEncoded[0]) + { + case ASN_NUMERICSTRING: + case ASN_PRINTABLESTRING: + case ASN_IA5STRING: + break; + default: + FIXME("Unimplemented string type %02x\n", pbEncoded[0]); + SetLastError(OSS_UNIMPLEMENTED); + return FALSE; + } + bytesNeeded = sizeof(CERT_NAME_VALUE); + if (pbEncoded[1]) + { + switch (pbEncoded[0]) + { + case ASN_NUMERICSTRING: + case ASN_PRINTABLESTRING: + case ASN_IA5STRING: + if (!(dwFlags & CRYPT_DECODE_NOCOPY_FLAG)) + bytesNeeded += pbEncoded[1]; + break; + } + } + if (!value) + { + *pcbValue = bytesNeeded; + return TRUE; + } + if (*pcbValue < bytesNeeded) + { + *pcbValue = bytesNeeded; + SetLastError(ERROR_MORE_DATA); + return FALSE; + } + *pcbValue = bytesNeeded; + switch (pbEncoded[0]) + { + case ASN_NUMERICSTRING: + value->dwValueType = CERT_RDN_NUMERIC_STRING; + break; + case ASN_PRINTABLESTRING: + value->dwValueType = CERT_RDN_PRINTABLE_STRING; + break; + case ASN_IA5STRING: + value->dwValueType = CERT_RDN_IA5_STRING; + break; + } + if (pbEncoded[1]) + { + switch (pbEncoded[0]) + { + case ASN_NUMERICSTRING: + case ASN_PRINTABLESTRING: + case ASN_IA5STRING: + value->Value.cbData = pbEncoded[1]; + if (dwFlags & CRYPT_DECODE_NOCOPY_FLAG) + value->Value.pbData = (BYTE *)pbEncoded + 2; + else + { + if (!value->Value.pbData) + { + SetLastError(CRYPT_E_ASN1_INTERNAL); + ret = FALSE; + } + else + memcpy(value->Value.pbData, pbEncoded + 2, pbEncoded[1]); + } + break; + } + } + else + { + value->Value.cbData = 0; + value->Value.pbData = NULL; + } + return ret; +} + +static BOOL WINAPI CRYPT_AsnDecodeRdnAttr(DWORD dwCertEncodingType, + const BYTE *pbEncoded, DWORD cbEncoded, DWORD dwFlags, CERT_RDN_ATTR *attr, + DWORD *pcbAttr) +{ + BOOL ret = TRUE; + DWORD bytesNeeded, size; + + /* cbEncoded is an upper bound on the number of bytes, not the actual + * count: check the count for sanity. It must be at least 6, two for the + * tag and length for the RDN_ATTR, two for the OID, and two for the string. + */ + if (cbEncoded < 6 || pbEncoded[1] < 4 || pbEncoded[1] > cbEncoded - 2) + { + SetLastError(CRYPT_E_ASN1_EOD); + return FALSE; + } + if (pbEncoded[0] != (ASN_CONSTRUCTOR | ASN_SEQUENCE)) + { + SetLastError(CRYPT_E_ASN1_BADTAG); + return FALSE; + } + bytesNeeded = sizeof(CERT_RDN_ATTR); + ret = CRYPT_AsnDecodeOid(dwCertEncodingType, pbEncoded + 2, + cbEncoded - 2, dwFlags, NULL, &size); + if (ret) + { + /* ugly: need to know the size of the next element of the sequence, + * so get it directly + */ + BYTE objIdLen = pbEncoded[3]; + + bytesNeeded += size; + /* hack: like encoding, this takes advantage of the fact that the rest + * of the structure is identical to a CERT_NAME_VALUE. + */ + ret = CRYPT_AsnDecodeNameValue(dwCertEncodingType, pbEncoded + 4 + + objIdLen, cbEncoded - 4 - objIdLen, dwFlags, NULL, &size); + if (ret) + { + bytesNeeded += size; + if (!attr) + *pcbAttr = bytesNeeded; + else if (*pcbAttr < bytesNeeded) + { + *pcbAttr = bytesNeeded; + SetLastError(ERROR_MORE_DATA); + ret = FALSE; + } + else + { + BYTE *originalData = attr->Value.pbData; + + *pcbAttr = bytesNeeded; + /* strange: decode the value first, because it has a counted + * size, and we can store the OID after it. Keep track of the + * original data pointer, we'll need to know whether it was + * changed. + */ + size = bytesNeeded; + ret = CRYPT_AsnDecodeNameValue(dwCertEncodingType, + pbEncoded + 4 + objIdLen, cbEncoded - 4 - objIdLen, + dwFlags, (CERT_NAME_VALUE *)&attr->dwValueType, &size); + if (ret) + { + if (objIdLen) + { + /* if the data were copied to the original location, + * the OID goes after. Otherwise it goes in the + * spot originally reserved for the data. + */ + if (attr->Value.pbData == originalData) + attr->pszObjId = (LPSTR)(attr->Value.pbData + + attr->Value.cbData); + else + attr->pszObjId = originalData; + size = bytesNeeded - size; + ret = CRYPT_AsnDecodeOid(dwCertEncodingType, + pbEncoded + 2, cbEncoded - 2, dwFlags, attr->pszObjId, + &size); + } + else + attr->pszObjId = NULL; + } + } + } + } + return ret; +} + +static BOOL WINAPI CRYPT_AsnDecodeRdn(DWORD dwCertEncodingType, + const BYTE *pbEncoded, DWORD cbEncoded, DWORD dwFlags, CERT_RDN *rdn, + DWORD *pcbRdn) +{ + BOOL ret = TRUE; + DWORD bytesNeeded, cRDNAttr = 0; + + /* cbEncoded is an upper bound on the number of bytes, not the actual + * count: check the count for sanity. + */ + if (cbEncoded <= 1 || pbEncoded[1] > cbEncoded - 2) + { + SetLastError(CRYPT_E_ASN1_EOD); + return FALSE; + } + if (pbEncoded[0] != (ASN_CONSTRUCTOR | ASN_SETOF)) + { + SetLastError(CRYPT_E_ASN1_BADTAG); + return FALSE; + } + bytesNeeded = sizeof(CERT_RDN); + if (pbEncoded[1]) + { + const BYTE *ptr; + DWORD size; + + for (ptr = pbEncoded + 2; ret && ptr - pbEncoded - 2 < pbEncoded[1]; ) + { + ret = CRYPT_AsnDecodeRdnAttr(dwCertEncodingType, ptr, + cbEncoded - (ptr - pbEncoded), dwFlags, NULL, &size); + if (ret) + { + cRDNAttr++; + bytesNeeded += size; + ptr += ptr[1] + 2; + } + } + } + if (ret) + { + if (!rdn) + { + *pcbRdn = bytesNeeded; + return TRUE; + } + if (*pcbRdn < bytesNeeded) + { + *pcbRdn = bytesNeeded; + SetLastError(ERROR_MORE_DATA); + return FALSE; + } + *pcbRdn = bytesNeeded; + rdn->cRDNAttr = cRDNAttr; + if (rdn->cRDNAttr == 0) + rdn->rgRDNAttr = NULL; + else + { + DWORD size, i; + BYTE *nextData; + const BYTE *ptr; + + rdn->rgRDNAttr = (CERT_RDN_ATTR *)((BYTE *)rdn + sizeof(CERT_RDN)); + nextData = (BYTE *)rdn->rgRDNAttr + + rdn->cRDNAttr * sizeof(CERT_RDN_ATTR); + for (i = 0, ptr = pbEncoded + 2; ret && i < cRDNAttr && + ptr - pbEncoded - 2 < pbEncoded[1]; i++) + { + rdn->rgRDNAttr[i].Value.pbData = nextData; + size = bytesNeeded; + ret = CRYPT_AsnDecodeRdnAttr(dwCertEncodingType, ptr, + cbEncoded - (ptr - pbEncoded), dwFlags, &rdn->rgRDNAttr[i], + &size); + if (ret) + { + bytesNeeded -= size; + /* If dwFlags & CRYPT_DECODE_NOCOPY_FLAG, the data may not + * have been copied. + */ + if (rdn->rgRDNAttr[i].Value.pbData == nextData) + nextData += rdn->rgRDNAttr[i].Value.cbData; + /* Ugly: the OID, if copied, is stored in memory after the + * value, so increment by its string length if it's set and + * points here. + */ + if ((const BYTE *)rdn->rgRDNAttr[i].pszObjId == nextData) + nextData += strlen(rdn->rgRDNAttr[i].pszObjId) + 1; + ptr += ptr[1] + 2; + } + } + } + } + return ret; +} + +static BOOL WINAPI CRYPT_AsnDecodeName(DWORD dwCertEncodingType, + LPCSTR lpszStructType, const BYTE *pbEncoded, DWORD cbEncoded, DWORD dwFlags, + PCRYPT_DECODE_PARA pDecodePara, void *pvStructInfo, DWORD *pcbStructInfo) +{ + BOOL ret = TRUE; + DWORD bytesNeeded, cRDN = 0; + + if (!pbEncoded || !cbEncoded) + { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + if (pbEncoded[0] != (ASN_CONSTRUCTOR | ASN_SEQUENCEOF)) + { + SetLastError(CRYPT_E_ASN1_BADTAG); + return FALSE; + } + if (cbEncoded <= 1) + { + SetLastError(CRYPT_E_ASN1_EOD); + return FALSE; + } + bytesNeeded = sizeof(CERT_NAME_INFO); + if (pbEncoded[1]) + { + const BYTE *ptr; + DWORD size; + + for (ptr = pbEncoded + 2; ret && ptr - pbEncoded - 2 < pbEncoded[1]; ) + { + ret = CRYPT_AsnDecodeRdn(dwCertEncodingType, ptr, + cbEncoded - (ptr - pbEncoded), dwFlags, NULL, &size); + if (ret) + { + cRDN++; + bytesNeeded += size; + ptr += ptr[1] + 2; + } + } + } + if (ret) + { + CERT_NAME_INFO *info; + + if (!pvStructInfo) + { + *pcbStructInfo = bytesNeeded; + return TRUE; + } + if (!CRYPT_DecodeEnsureSpace(dwFlags, pDecodePara, pvStructInfo, + pcbStructInfo, bytesNeeded)) + return FALSE; + if (dwFlags & CRYPT_DECODE_ALLOC_FLAG) + pvStructInfo = *(BYTE **)pvStructInfo; + info = (CERT_NAME_INFO *)pvStructInfo; + info->cRDN = cRDN; + if (info->cRDN == 0) + info->rgRDN = NULL; + else + { + DWORD size, i; + BYTE *nextData; + const BYTE *ptr; + + info->rgRDN = (CERT_RDN *)((BYTE *)pvStructInfo + + sizeof(CERT_NAME_INFO)); + nextData = (BYTE *)info->rgRDN + info->cRDN * sizeof(CERT_RDN); + for (i = 0, ptr = pbEncoded + 2; ret && i < cRDN && + ptr - pbEncoded - 2 < pbEncoded[1]; i++) + { + info->rgRDN[i].rgRDNAttr = (CERT_RDN_ATTR *)nextData; + size = bytesNeeded; + ret = CRYPT_AsnDecodeRdn(dwCertEncodingType, ptr, + cbEncoded - (ptr - pbEncoded), dwFlags, &info->rgRDN[i], + &size); + if (ret) + { + nextData += size; + bytesNeeded -= size; + ptr += ptr[1] + 2; + } + } + } + } + return ret; +} + static BOOL WINAPI CRYPT_AsnDecodeInt(DWORD dwCertEncodingType, LPCSTR lpszStructType, const BYTE *pbEncoded, DWORD cbEncoded, DWORD dwFlags, PCRYPT_DECODE_PARA pDecodePara, void *pvStructInfo, DWORD *pcbStructInfo) @@ -657,7 +1483,7 @@ static BOOL WINAPI CRYPT_AsnDecodeInt(DWORD dwCertEncodingType, if (!pbEncoded || !cbEncoded) { - SetLastError(ERROR_INVALID_PARAMETER); + SetLastError(CRYPT_E_ASN1_EOD); return FALSE; } if (!pvStructInfo) @@ -670,6 +1496,11 @@ static BOOL WINAPI CRYPT_AsnDecodeInt(DWORD dwCertEncodingType, SetLastError(CRYPT_E_ASN1_BADTAG); return FALSE; } + if (cbEncoded <= 1) + { + SetLastError(CRYPT_E_ASN1_EOD); + return FALSE; + } if (pbEncoded[1] == 0) { SetLastError(CRYPT_E_ASN1_CORRUPT); @@ -719,12 +1550,68 @@ static BOOL WINAPI CRYPT_AsnDecodeInt(DWORD dwCertEncodingType, } \ } while (0) +static BOOL CRYPT_AsnDecodeTimeZone(const BYTE *pbEncoded, DWORD len, + SYSTEMTIME *sysTime) +{ + BOOL ret = TRUE; + + if (len >= 3 && (*pbEncoded == '+' || *pbEncoded == '-')) + { + WORD hours, minutes = 0; + BYTE sign = *pbEncoded++; + + len--; + CRYPT_TIME_GET_DIGITS(pbEncoded, len, 2, hours); + if (hours >= 24) + { + SetLastError(CRYPT_E_ASN1_CORRUPT); + ret = FALSE; + goto end; + } + if (len >= 2) + { + CRYPT_TIME_GET_DIGITS(pbEncoded, len, 2, minutes); + if (minutes >= 60) + { + SetLastError(CRYPT_E_ASN1_CORRUPT); + ret = FALSE; + goto end; + } + } + if (sign == '+') + { + sysTime->wHour += hours; + sysTime->wMinute += minutes; + } + else + { + if (hours > sysTime->wHour) + { + sysTime->wDay--; + sysTime->wHour = 24 - (hours - sysTime->wHour); + } + else + sysTime->wHour -= hours; + if (minutes > sysTime->wMinute) + { + sysTime->wHour--; + sysTime->wMinute = 60 - (minutes - sysTime->wMinute); + } + else + sysTime->wMinute -= minutes; + } + } +end: + return ret; +} + static BOOL WINAPI CRYPT_AsnDecodeUtcTime(DWORD dwCertEncodingType, LPCSTR lpszStructType, const BYTE *pbEncoded, DWORD cbEncoded, DWORD dwFlags, PCRYPT_DECODE_PARA pDecodePara, void *pvStructInfo, DWORD *pcbStructInfo) { SYSTEMTIME sysTime = { 0 }; BYTE len; + BOOL ret = TRUE; if (!pbEncoded || !cbEncoded) { @@ -741,6 +1628,11 @@ static BOOL WINAPI CRYPT_AsnDecodeUtcTime(DWORD dwCertEncodingType, SetLastError(CRYPT_E_ASN1_BADTAG); return FALSE; } + if (cbEncoded <= 1) + { + SetLastError(CRYPT_E_ASN1_EOD); + return FALSE; + } len = pbEncoded[1]; /* FIXME: magic # */ if (len < 10) @@ -764,19 +1656,22 @@ static BOOL WINAPI CRYPT_AsnDecodeUtcTime(DWORD dwCertEncodingType, CRYPT_TIME_GET_DIGITS(pbEncoded, len, 2, sysTime.wSecond); else if (isdigit(*pbEncoded)) CRYPT_TIME_GET_DIGITS(pbEncoded, len, 1, sysTime.wSecond); - if (len > 0) + ret = CRYPT_AsnDecodeTimeZone(pbEncoded, len, &sysTime); + } + if (ret) + { + if (!CRYPT_DecodeEnsureSpace(dwFlags, pDecodePara, pvStructInfo, + pcbStructInfo, sizeof(FILETIME))) + ret = FALSE; + else { - /* FIXME: get timezone, for now assuming UTC (no adjustment) */ + if (dwFlags & CRYPT_DECODE_ALLOC_FLAG) + pvStructInfo = *(BYTE **)pvStructInfo; + *pcbStructInfo = sizeof(FILETIME); + ret = SystemTimeToFileTime(&sysTime, (FILETIME *)pvStructInfo); } } - - if (!CRYPT_DecodeEnsureSpace(dwFlags, pDecodePara, pvStructInfo, - pcbStructInfo, sizeof(FILETIME))) - return FALSE; - if (dwFlags & CRYPT_DECODE_ALLOC_FLAG) - pvStructInfo = *(BYTE **)pvStructInfo; - *pcbStructInfo = sizeof(FILETIME); - return SystemTimeToFileTime(&sysTime, (FILETIME *)pvStructInfo); + return ret; } static BOOL WINAPI CRYPT_AsnDecodeGeneralizedTime(DWORD dwCertEncodingType, @@ -785,6 +1680,7 @@ static BOOL WINAPI CRYPT_AsnDecodeGeneralizedTime(DWORD dwCertEncodingType, { SYSTEMTIME sysTime = { 0 }; BYTE len; + BOOL ret = TRUE; if (!pbEncoded || !cbEncoded) { @@ -801,6 +1697,11 @@ static BOOL WINAPI CRYPT_AsnDecodeGeneralizedTime(DWORD dwCertEncodingType, SetLastError(CRYPT_E_ASN1_BADTAG); return FALSE; } + if (cbEncoded <= 1) + { + SetLastError(CRYPT_E_ASN1_EOD); + return FALSE; + } len = pbEncoded[1]; /* FIXME: magic # */ if (len < 10) @@ -828,50 +1729,22 @@ static BOOL WINAPI CRYPT_AsnDecodeGeneralizedTime(DWORD dwCertEncodingType, CRYPT_TIME_GET_DIGITS(pbEncoded, len, digits, sysTime.wMilliseconds); } - if (len >= 5 && (*pbEncoded == '+' || *pbEncoded == '-')) + ret = CRYPT_AsnDecodeTimeZone(pbEncoded, len, &sysTime); + } + if (ret) + { + if (!CRYPT_DecodeEnsureSpace(dwFlags, pDecodePara, pvStructInfo, + pcbStructInfo, sizeof(FILETIME))) + ret = FALSE; + else { - WORD hours, minutes; - BYTE sign = *pbEncoded++; - - len--; - CRYPT_TIME_GET_DIGITS(pbEncoded, len, 2, hours); - if (hours >= 24) - return CRYPT_E_ASN1_CORRUPT; - CRYPT_TIME_GET_DIGITS(pbEncoded, len, 2, minutes); - if (minutes >= 60) - return CRYPT_E_ASN1_CORRUPT; - if (sign == '+') - { - sysTime.wHour += hours; - sysTime.wMinute += minutes; - } - else - { - if (hours > sysTime.wHour) - { - sysTime.wDay--; - sysTime.wHour = 24 - (hours - sysTime.wHour); - } - else - sysTime.wHour -= hours; - if (minutes > sysTime.wMinute) - { - sysTime.wHour--; - sysTime.wMinute = 60 - (minutes - sysTime.wMinute); - } - else - sysTime.wMinute -= minutes; - } + if (dwFlags & CRYPT_DECODE_ALLOC_FLAG) + pvStructInfo = *(BYTE **)pvStructInfo; + *pcbStructInfo = sizeof(FILETIME); + ret = SystemTimeToFileTime(&sysTime, (FILETIME *)pvStructInfo); } } - - if (!CRYPT_DecodeEnsureSpace(dwFlags, pDecodePara, pvStructInfo, - pcbStructInfo, sizeof(FILETIME))) - return FALSE; - if (dwFlags & CRYPT_DECODE_ALLOC_FLAG) - pvStructInfo = *(BYTE **)pvStructInfo; - *pcbStructInfo = sizeof(FILETIME); - return SystemTimeToFileTime(&sysTime, (FILETIME *)pvStructInfo); + return ret; } static BOOL WINAPI CRYPT_AsnDecodeChoiceOfTime(DWORD dwCertEncodingType, @@ -940,6 +1813,9 @@ BOOL WINAPI CryptDecodeObjectEx(DWORD dwCertEncodingType, LPCSTR lpszStructType, { switch (LOWORD(lpszStructType)) { + case (WORD)X509_NAME: + decodeFunc = CRYPT_AsnDecodeName; + break; case (WORD)X509_INTEGER: decodeFunc = CRYPT_AsnDecodeInt; break; diff --git a/dlls/crypt32/tests/encode.c b/dlls/crypt32/tests/encode.c index 822f7c88626..6dbcf24c06b 100644 --- a/dlls/crypt32/tests/encode.c +++ b/dlls/crypt32/tests/encode.c @@ -42,7 +42,7 @@ static const struct encodedInt ints[] = { { 0xbaddf00d, { 2, 4, 0xba, 0xdd, 0xf0, 0x0d } }, }; -static void test_encodeint(void) +static void test_encodeint(DWORD dwEncoding) { DWORD bufSize = 0; int i; @@ -60,33 +60,35 @@ static void test_encodeint(void) /* check with NULL integer buffer. Windows XP incorrectly returns an * NTSTATUS. */ - ret = CryptEncodeObjectEx(X509_ASN_ENCODING, X509_INTEGER, NULL, 0, NULL, - NULL, &bufSize); - ok(!ret && (GetLastError() == ERROR_INVALID_PARAMETER || GetLastError() == - STATUS_ACCESS_VIOLATION), "Unexpected error code %ld\n", GetLastError()); + ret = CryptEncodeObjectEx(dwEncoding, X509_INTEGER, NULL, 0, NULL, NULL, + &bufSize); + ok(!ret && GetLastError() == STATUS_ACCESS_VIOLATION, + "Expected STATUS_ACCESS_VIOLATION, got %08lx\n", GetLastError()); for (i = 0; i < sizeof(ints) / sizeof(ints[0]); i++) { BYTE *buf = NULL; - ret = CryptEncodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, - X509_INTEGER, &ints[i].val, 0, NULL, NULL, &bufSize); + ret = CryptEncodeObjectEx(dwEncoding, X509_INTEGER, &ints[i].val, 0, + NULL, NULL, &bufSize); ok(ret, "Expected success, got %ld\n", GetLastError()); - ret = CryptEncodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, - X509_INTEGER, &ints[i].val, CRYPT_ENCODE_ALLOC_FLAG, NULL, - (BYTE *)&buf, &bufSize); + ret = CryptEncodeObjectEx(dwEncoding, X509_INTEGER, &ints[i].val, + CRYPT_ENCODE_ALLOC_FLAG, NULL, (BYTE *)&buf, &bufSize); ok(ret, "CryptEncodeObjectEx failed: %ld\n", GetLastError()); - ok(buf[0] == 2, "Got unexpected type %d for integer (expected 2)\n", - buf[0]); - ok(!memcmp(buf + 1, ints[i].encoded + 1, ints[i].encoded[1] + 1), - "Encoded value of 0x%08x didn't match expected\n", ints[i].val); - LocalFree(buf); + if (buf) + { + ok(buf[0] == 2, "Got unexpected type %d for integer (expected 2)\n", + buf[0]); + ok(!memcmp(buf + 1, ints[i].encoded + 1, ints[i].encoded[1] + 1), + "Encoded value of 0x%08x didn't match expected\n", ints[i].val); + LocalFree(buf); + } } } -static void test_decodeint(void) +static void test_decodeint(DWORD dwEncoding) { static const char bigInt[] = { 2, 5, 0xff, 0xfe, 0xff, 0xfe, 0xff }; - static const char testStr[] = { 16, 4, 't', 'e', 's', 't' }; + static const char testStr[] = { 0x16, 4, 't', 'e', 's', 't' }; BYTE *buf = NULL; DWORD bufSize = 0; int i; @@ -101,36 +103,33 @@ static void test_decodeint(void) ints[0].encoded[1] + 2, 0, NULL, NULL, &bufSize); ok(!ret && GetLastError() == ERROR_FILE_NOT_FOUND, "Expected ERROR_FILE_NOT_FOUND, got %ld\n", GetLastError()); - /* check with NULL integer buffer. Windows XP returns an apparently random - * error code (0x01c567df). - */ - ret = CryptDecodeObjectEx(X509_ASN_ENCODING, X509_INTEGER, NULL, 0, 0, - NULL, NULL, &bufSize); - ok(!ret, "Expected failure, got success\n"); + /* check with NULL integer buffer */ + ret = CryptDecodeObjectEx(dwEncoding, X509_INTEGER, NULL, 0, 0, NULL, NULL, + &bufSize); + ok(!ret && GetLastError() == CRYPT_E_ASN1_EOD, + "Expected CRYPT_E_ASN1_EOD, got %08lx\n", GetLastError()); /* check with a valid, but too large, integer */ - ret = CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, - X509_INTEGER, bigInt, bigInt[1] + 2, CRYPT_ENCODE_ALLOC_FLAG, NULL, - (BYTE *)&buf, &bufSize); + ret = CryptDecodeObjectEx(dwEncoding, X509_INTEGER, bigInt, bigInt[1] + 2, + CRYPT_DECODE_ALLOC_FLAG, NULL, (BYTE *)&buf, &bufSize); ok(!ret && GetLastError() == CRYPT_E_ASN1_LARGE, "Expected CRYPT_E_ASN1_LARGE, got %ld\n", GetLastError()); /* check with a DER-encoded string */ - ret = CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, - X509_INTEGER, testStr, testStr[1] + 2, CRYPT_ENCODE_ALLOC_FLAG, NULL, - (BYTE *)&buf, &bufSize); + ret = CryptDecodeObjectEx(dwEncoding, X509_INTEGER, testStr, testStr[1] + 2, + CRYPT_DECODE_ALLOC_FLAG, NULL, (BYTE *)&buf, &bufSize); ok(!ret && GetLastError() == CRYPT_E_ASN1_BADTAG, "Expected CRYPT_E_ASN1_BADTAG, got %ld\n", GetLastError()); for (i = 0; i < sizeof(ints) / sizeof(ints[0]); i++) { /* When the output buffer is NULL, this always succeeds */ SetLastError(0xdeadbeef); - ret = CryptDecodeObjectEx(X509_ASN_ENCODING, X509_INTEGER, + ret = CryptDecodeObjectEx(dwEncoding, X509_INTEGER, (BYTE *)&ints[i].encoded, ints[i].encoded[1] + 2, 0, NULL, NULL, &bufSize); ok(ret && GetLastError() == NOERROR, "Expected success and NOERROR, got %ld\n", GetLastError()); - ret = CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, - X509_INTEGER, (BYTE *)&ints[i].encoded, ints[i].encoded[1] + 2, - CRYPT_ENCODE_ALLOC_FLAG, NULL, (BYTE *)&buf, &bufSize); + ret = CryptDecodeObjectEx(dwEncoding, X509_INTEGER, + (BYTE *)&ints[i].encoded, ints[i].encoded[1] + 2, + CRYPT_DECODE_ALLOC_FLAG, NULL, (BYTE *)&buf, &bufSize); ok(ret, "CryptDecodeObjectEx failed: %ld\n", GetLastError()); ok(bufSize == sizeof(int), "Expected size %d, got %ld\n", sizeof(int), bufSize); @@ -150,7 +149,7 @@ struct encodedFiletime BYTE *encodedTime; }; -static void testTimeEncoding(LPCSTR encoding, +static void testTimeEncoding(DWORD dwEncoding, LPCSTR structType, const struct encodedFiletime *time) { FILETIME ft = { 0 }; @@ -160,15 +159,12 @@ static void testTimeEncoding(LPCSTR encoding, ret = SystemTimeToFileTime(&time->sysTime, &ft); ok(ret, "SystemTimeToFileTime failed: %ld\n", GetLastError()); - /* No test case, but both X509_ASN_ENCODING and PKCS_7_ASN_ENCODING have - * the same effect for time encodings. - */ - ret = CryptEncodeObjectEx(X509_ASN_ENCODING, encoding, &ft, + ret = CryptEncodeObjectEx(dwEncoding, structType, &ft, CRYPT_ENCODE_ALLOC_FLAG, NULL, (BYTE *)&buf, &bufSize); /* years other than 1950-2050 are not allowed for encodings other than * X509_CHOICE_OF_TIME. */ - if (encoding == X509_CHOICE_OF_TIME || + if (structType == X509_CHOICE_OF_TIME || (time->sysTime.wYear >= 1950 && time->sysTime.wYear <= 2050)) { ok(ret, "CryptEncodeObjectEx failed: %ld (0x%08lx)\n", GetLastError(), @@ -191,7 +187,7 @@ static void testTimeEncoding(LPCSTR encoding, "Expected CRYPT_E_BAD_ENCODE, got 0x%08lx\n", GetLastError()); } -static void testTimeDecoding(LPCSTR encoding, +static void testTimeDecoding(DWORD dwEncoding, LPCSTR structType, const struct encodedFiletime *time) { FILETIME ft1 = { 0 }, ft2 = { 0 }; @@ -200,15 +196,12 @@ static void testTimeDecoding(LPCSTR encoding, ret = SystemTimeToFileTime(&time->sysTime, &ft1); ok(ret, "SystemTimeToFileTime failed: %ld\n", GetLastError()); - /* No test case, but both X509_ASN_ENCODING and PKCS_7_ASN_ENCODING have - * the same effect for time encodings. - */ - ret = CryptDecodeObjectEx(X509_ASN_ENCODING, encoding, time->encodedTime, + ret = CryptDecodeObjectEx(dwEncoding, structType, time->encodedTime, time->encodedTime[1] + 2, 0, NULL, &ft2, &size); /* years other than 1950-2050 are not allowed for encodings other than * X509_CHOICE_OF_TIME. */ - if (encoding == X509_CHOICE_OF_TIME || + if (structType == X509_CHOICE_OF_TIME || (time->sysTime.wYear >= 1950 && time->sysTime.wYear <= 2050)) { ok(ret, "CryptDecodeObjectEx failed: %ld (0x%08lx)\n", GetLastError(), @@ -227,19 +220,19 @@ static const struct encodedFiletime times[] = { { { 2145, 6, 1, 6, 16, 10, 0, 0 }, "\x18" "\x0f" "21450606161000Z" }, }; -static void test_encodeFiletime(void) +static void test_encodeFiletime(DWORD dwEncoding) { DWORD i; for (i = 0; i < sizeof(times) / sizeof(times[0]); i++) { - testTimeEncoding(X509_CHOICE_OF_TIME, ×[i]); - testTimeEncoding(PKCS_UTC_TIME, ×[i]); - testTimeEncoding(szOID_RSA_signingTime, ×[i]); + testTimeEncoding(dwEncoding, X509_CHOICE_OF_TIME, ×[i]); + testTimeEncoding(dwEncoding, PKCS_UTC_TIME, ×[i]); + testTimeEncoding(dwEncoding, szOID_RSA_signingTime, ×[i]); } } -static void test_decodeFiletime(void) +static void test_decodeFiletime(DWORD dwEncoding) { static const struct encodedFiletime otherTimes[] = { { { 1945, 6, 1, 6, 16, 10, 0, 0 }, "\x18" "\x13" "19450606161000.000Z" }, @@ -249,6 +242,11 @@ static void test_decodeFiletime(void) { { 1945, 6, 1, 6, 14, 55, 0, 0 }, "\x18" "\x13" "19450606161000-0115" }, { { 2145, 6, 1, 6, 16, 0, 0, 0 }, "\x18" "\x0a" "2145060616" }, { { 2045, 6, 1, 6, 16, 10, 0, 0 }, "\x17" "\x0a" "4506061610" }, + { { 2045, 6, 1, 6, 16, 10, 0, 0 }, "\x17" "\x0b" "4506061610Z" }, + { { 2045, 6, 1, 6, 17, 10, 0, 0 }, "\x17" "\x0d" "4506061610+01" }, + { { 2045, 6, 1, 6, 15, 10, 0, 0 }, "\x17" "\x0d" "4506061610-01" }, + { { 2045, 6, 1, 6, 17, 10, 0, 0 }, "\x17" "\x0f" "4506061610+0100" }, + { { 2045, 6, 1, 6, 15, 10, 0, 0 }, "\x17" "\x0f" "4506061610-0100" }, }; /* An oddball case that succeeds in Windows, but doesn't seem correct { { 2145, 6, 1, 2, 11, 31, 0, 0 }, "\x18" "\x13" "21450606161000-9999" }, @@ -270,33 +268,322 @@ static void test_decodeFiletime(void) ret = SystemTimeToFileTime(×[0].sysTime, &ft1); ok(ret, "SystemTimeToFileTime failed: %ld\n", GetLastError()); size = 1; - ret = CryptDecodeObjectEx(X509_ASN_ENCODING, X509_CHOICE_OF_TIME, + ret = CryptDecodeObjectEx(dwEncoding, X509_CHOICE_OF_TIME, times[0].encodedTime, times[0].encodedTime[1] + 2, 0, NULL, &ft2, &size); ok(!ret && GetLastError() == ERROR_MORE_DATA, "Expected ERROR_MORE_DATA, got %ld\n", GetLastError()); /* Normal tests */ for (i = 0; i < sizeof(times) / sizeof(times[0]); i++) { - testTimeDecoding(X509_CHOICE_OF_TIME, ×[i]); - testTimeDecoding(PKCS_UTC_TIME, ×[i]); - testTimeDecoding(szOID_RSA_signingTime, ×[i]); + testTimeDecoding(dwEncoding, X509_CHOICE_OF_TIME, ×[i]); + testTimeDecoding(dwEncoding, PKCS_UTC_TIME, ×[i]); + testTimeDecoding(dwEncoding, szOID_RSA_signingTime, ×[i]); } for (i = 0; i < sizeof(otherTimes) / sizeof(otherTimes[0]); i++) { - testTimeDecoding(X509_CHOICE_OF_TIME, &otherTimes[i]); - testTimeDecoding(PKCS_UTC_TIME, &otherTimes[i]); - testTimeDecoding(szOID_RSA_signingTime, &otherTimes[i]); + testTimeDecoding(dwEncoding, X509_CHOICE_OF_TIME, &otherTimes[i]); + testTimeDecoding(dwEncoding, PKCS_UTC_TIME, &otherTimes[i]); + testTimeDecoding(dwEncoding, szOID_RSA_signingTime, &otherTimes[i]); } for (i = 0; i < sizeof(bogusTimes) / sizeof(bogusTimes[0]); i++) { size = sizeof(ft1); - ret = CryptDecodeObjectEx(X509_ASN_ENCODING, X509_CHOICE_OF_TIME, + ret = CryptDecodeObjectEx(dwEncoding, X509_CHOICE_OF_TIME, bogusTimes[i], bogusTimes[i][1] + 2, 0, NULL, &ft1, &size); ok(!ret && GetLastError() == CRYPT_E_ASN1_CORRUPT, "Expected CRYPT_E_ASN1_CORRUPT, got %08lx\n", GetLastError()); } } +struct EncodedName +{ + CERT_RDN_ATTR attr; + BYTE *encoded; +}; + +static const char commonName[] = "Juan Lang"; +static const char surName[] = "Lang"; +static const char bogusIA5[] = "\x80"; +static const char bogusPrintable[] = "~"; +static const char bogusNumeric[] = "A"; +static const struct EncodedName names[] = { + { { szOID_COMMON_NAME, CERT_RDN_PRINTABLE_STRING, + { sizeof(commonName), (BYTE *)commonName } }, + "\x30\x15\x31\x13\x30\x11\x06\x03\x55\x04\x03\x13\x0aJuan Lang" }, + { { szOID_COMMON_NAME, CERT_RDN_IA5_STRING, + { sizeof(commonName), (BYTE *)commonName } }, + "\x30\x15\x31\x13\x30\x11\x06\x03\x55\x04\x03\x16\x0aJuan Lang" }, + { { szOID_SUR_NAME, CERT_RDN_IA5_STRING, + { sizeof(surName), (BYTE *)surName } }, + "\x30\x10\x31\x0e\x30\x0c\x06\x03\x55\x04\x04\x16\x05Lang" }, + { { NULL, CERT_RDN_PRINTABLE_STRING, + { sizeof(commonName), (BYTE *)commonName } }, + "\x30\x12\x31\x10\x30\x0e\x06\x00\x13\x0aJuan Lang" }, +/* The following test isn't a very good one, because it doesn't encode any + * Japanese characters. I'm leaving it out for now. + { { szOID_COMMON_NAME, CERT_RDN_T61_STRING, + { sizeof(commonName), (BYTE *)commonName } }, + "\x30\x15\x31\x13\x30\x11\x06\x03\x55\x04\x03\x14\x0aJuan Lang" }, + */ + /* The following tests succeed under Windows, but really should fail, + * they contain characters that are illegal for the encoding. I'm + * including them to justify my lazy encoding. + */ + { { szOID_COMMON_NAME, CERT_RDN_IA5_STRING, + { sizeof(bogusIA5), (BYTE *)bogusIA5 } }, + "\x30\x0d\x31\x0b\x30\x09\x06\x03\x55\x04\x03\x16\x02\x80" }, + { { szOID_COMMON_NAME, CERT_RDN_PRINTABLE_STRING, + { sizeof(bogusPrintable), (BYTE *)bogusPrintable } }, + "\x30\x0d\x31\x0b\x30\x09\x06\x03\x55\x04\x03\x13\x02\x7e" }, + { { szOID_COMMON_NAME, CERT_RDN_NUMERIC_STRING, + { sizeof(bogusNumeric), (BYTE *)bogusNumeric } }, + "\x30\x0d\x31\x0b\x30\x09\x06\x03\x55\x04\x03\x12\x02\x41" }, +}; + +static const BYTE emptyName[] = { 0x30, 0 }; +static const BYTE emptyRDNs[] = { 0x30, 0x02, 0x31, 0 }; +static const BYTE twoRDNs[] = "\x30\x23\x31\x21\x30\x0c\x06\x03\x55\x04\x04" + "\x13\x05\x4c\x61\x6e\x67\x00\x30\x11\x06\x03\x55\x04\x03" + "\x13\x0a\x4a\x75\x61\x6e\x20\x4c\x61\x6e\x67"; + +static void test_encodeName(DWORD dwEncoding) +{ + CERT_RDN_ATTR attrs[2]; + CERT_RDN rdn; + CERT_NAME_INFO info; + BYTE *buf = NULL; + DWORD size = 0, i; + BOOL ret; + + /* Test with NULL pvStructInfo */ + ret = CryptEncodeObjectEx(dwEncoding, X509_NAME, NULL, + CRYPT_ENCODE_ALLOC_FLAG, NULL, (BYTE *)&buf, &size); + ok(!ret && GetLastError() == STATUS_ACCESS_VIOLATION, + "Expected STATUS_ACCESS_VIOLATION, got %08lx\n", GetLastError()); + /* Test with empty CERT_NAME_INFO */ + info.cRDN = 0; + info.rgRDN = NULL; + ret = CryptEncodeObjectEx(dwEncoding, X509_NAME, &info, + CRYPT_ENCODE_ALLOC_FLAG, NULL, (BYTE *)&buf, &size); + ok(ret, "CryptEncodeObjectEx failed: %08lx\n", GetLastError()); + if (buf) + { + ok(!memcmp(buf, emptyName, sizeof(emptyName)), + "Got unexpected encoding for empty name\n"); + LocalFree(buf); + } + /* Test with bogus CERT_RDN */ + info.cRDN = 1; + ret = CryptEncodeObjectEx(dwEncoding, X509_NAME, &info, + CRYPT_ENCODE_ALLOC_FLAG, NULL, (BYTE *)&buf, &size); + ok(!ret && GetLastError() == STATUS_ACCESS_VIOLATION, + "Expected STATUS_ACCESS_VIOLATION, got %08lx\n", GetLastError()); + /* Test with empty CERT_RDN */ + rdn.cRDNAttr = 0; + rdn.rgRDNAttr = NULL; + info.cRDN = 1; + info.rgRDN = &rdn; + ret = CryptEncodeObjectEx(dwEncoding, X509_NAME, &info, + CRYPT_ENCODE_ALLOC_FLAG, NULL, (BYTE *)&buf, &size); + ok(ret, "CryptEncodeObjectEx failed: %08lx\n", GetLastError()); + if (buf) + { + ok(!memcmp(buf, emptyRDNs, sizeof(emptyRDNs)), + "Got unexpected encoding for empty RDN array\n"); + LocalFree(buf); + } + /* Test with bogus attr array */ + rdn.cRDNAttr = 1; + rdn.rgRDNAttr = NULL; + ret = CryptEncodeObjectEx(dwEncoding, X509_NAME, &info, + CRYPT_ENCODE_ALLOC_FLAG, NULL, (BYTE *)&buf, &size); + ok(!ret && GetLastError() == STATUS_ACCESS_VIOLATION, + "Expected STATUS_ACCESS_VIOLATION, got %08lx\n", GetLastError()); + /* oddly, a bogus OID is accepted by Windows XP; not testing. + attrs[0].pszObjId = "bogus"; + attrs[0].dwValueType = CERT_RDN_PRINTABLE_STRING; + attrs[0].Value.cbData = sizeof(commonName); + attrs[0].Value.pbData = (BYTE *)commonName; + rdn.cRDNAttr = 1; + rdn.rgRDNAttr = attrs; + ret = CryptEncodeObjectEx(dwEncoding, X509_NAME, &info, + CRYPT_ENCODE_ALLOC_FLAG, NULL, (BYTE *)&buf, &size); + ok(!ret, "Expected failure, got success\n"); + */ + /* Check with two CERT_RDN_ATTRs. Note DER encoding forces the order of + * the encoded attributes to be swapped. + */ + attrs[0].pszObjId = szOID_COMMON_NAME; + attrs[0].dwValueType = CERT_RDN_PRINTABLE_STRING; + attrs[0].Value.cbData = sizeof(commonName); + attrs[0].Value.pbData = (BYTE *)commonName; + attrs[1].pszObjId = szOID_SUR_NAME; + attrs[1].dwValueType = CERT_RDN_PRINTABLE_STRING; + attrs[1].Value.cbData = sizeof(surName); + attrs[1].Value.pbData = (BYTE *)surName; + rdn.cRDNAttr = 2; + rdn.rgRDNAttr = attrs; + ret = CryptEncodeObjectEx(dwEncoding, X509_NAME, &info, + CRYPT_ENCODE_ALLOC_FLAG, NULL, (BYTE *)&buf, &size); + ok(ret, "CryptEncodeObjectEx failed: %08lx\n", GetLastError()); + if (buf) + { + ok(!memcmp(buf, twoRDNs, sizeof(twoRDNs)), + "Got unexpected encoding for two RDN array\n"); + LocalFree(buf); + } + /* CERT_RDN_ANY_TYPE is too vague for X509_NAMEs, check the return */ + rdn.cRDNAttr = 1; + attrs[0].dwValueType = CERT_RDN_ANY_TYPE; + ret = CryptEncodeObjectEx(dwEncoding, X509_NAME, &info, + CRYPT_ENCODE_ALLOC_FLAG, NULL, (BYTE *)&buf, &size); + ok(!ret && GetLastError() == HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER), + "Expected ERROR_INVALID_PARAMETER, got %08lx\n", GetLastError()); + for (i = 0; i < sizeof(names) / sizeof(names[0]); i++) + { + rdn.cRDNAttr = 1; + rdn.rgRDNAttr = (CERT_RDN_ATTR *)&names[i].attr; + ret = CryptEncodeObjectEx(dwEncoding, X509_NAME, &info, + CRYPT_ENCODE_ALLOC_FLAG, NULL, (BYTE *)&buf, &size); + ok(ret, "CryptEncodeObjectEx failed: %08lx\n", GetLastError()); + if (buf) + { + ok(size == names[i].encoded[1] + 2, "Expected size %d, got %ld\n", + names[i].encoded[1] + 2, size); + ok(!memcmp(buf, names[i].encoded, names[i].encoded[1] + 2), + "Got unexpected encoding\n"); + LocalFree(buf); + } + } +} + +static void compareNames(const CERT_NAME_INFO *expected, + const CERT_NAME_INFO *got) +{ + ok(got->cRDN == expected->cRDN, "Expected %ld RDNs, got %ld\n", + expected->cRDN, got->cRDN); + if (expected->cRDN) + { + ok(got->rgRDN[0].cRDNAttr == expected->rgRDN[0].cRDNAttr, + "Expected %ld RDN attrs, got %ld\n", expected->rgRDN[0].cRDNAttr, + got->rgRDN[0].cRDNAttr); + if (expected->rgRDN[0].cRDNAttr) + { + if (expected->rgRDN[0].rgRDNAttr[0].pszObjId && + strlen(expected->rgRDN[0].rgRDNAttr[0].pszObjId)) + { + ok(got->rgRDN[0].rgRDNAttr[0].pszObjId != NULL, + "Expected OID %s, got NULL\n", + expected->rgRDN[0].rgRDNAttr[0].pszObjId); + if (got->rgRDN[0].rgRDNAttr[0].pszObjId) + ok(!strcmp(got->rgRDN[0].rgRDNAttr[0].pszObjId, + expected->rgRDN[0].rgRDNAttr[0].pszObjId), + "Got unexpected OID %s, expected %s\n", + got->rgRDN[0].rgRDNAttr[0].pszObjId, + expected->rgRDN[0].rgRDNAttr[0].pszObjId); + } + ok(got->rgRDN[0].rgRDNAttr[0].Value.cbData == + expected->rgRDN[0].rgRDNAttr[0].Value.cbData, + "Unexpected data size, got %ld, expected %ld\n", + got->rgRDN[0].rgRDNAttr[0].Value.cbData, + expected->rgRDN[0].rgRDNAttr[0].Value.cbData); + if (expected->rgRDN[0].rgRDNAttr[0].Value.pbData) + ok(!memcmp(got->rgRDN[0].rgRDNAttr[0].Value.pbData, + expected->rgRDN[0].rgRDNAttr[0].Value.pbData, + expected->rgRDN[0].rgRDNAttr[0].Value.cbData), + "Unexpected value\n"); + } + } +} + +static void test_decodeName(DWORD dwEncoding) +{ + int i; + BYTE *buf = NULL; + DWORD bufSize = 0; + BOOL ret; + CERT_RDN rdn; + CERT_NAME_INFO info = { 1, &rdn }; + + for (i = 0; i < sizeof(names) / sizeof(names[0]); i++) + { + /* When the output buffer is NULL, this always succeeds */ + SetLastError(0xdeadbeef); + ret = CryptDecodeObjectEx(dwEncoding, X509_NAME, names[i].encoded, + names[i].encoded[1] + 2, 0, NULL, NULL, &bufSize); + ok(ret && GetLastError() == NOERROR, + "Expected success and NOERROR, got %08lx\n", GetLastError()); + ret = CryptDecodeObjectEx(dwEncoding, X509_NAME, names[i].encoded, + names[i].encoded[1] + 2, + CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_SHARE_OID_STRING_FLAG, NULL, + (BYTE *)&buf, &bufSize); + ok(ret, "CryptDecodeObjectEx failed: %08lx\n", GetLastError()); + rdn.cRDNAttr = 1; + rdn.rgRDNAttr = (CERT_RDN_ATTR *)&names[i].attr; + if (buf) + { + compareNames((CERT_NAME_INFO *)buf, &info); + LocalFree(buf); + } + } + /* test empty name */ + bufSize = 0; + ret = CryptDecodeObjectEx(dwEncoding, X509_NAME, emptyName, + emptyName[1] + 2, + CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_SHARE_OID_STRING_FLAG, NULL, + (BYTE *)&buf, &bufSize); + ok(ret, "CryptDecodeObjectEx failed: %08lx\n", GetLastError()); + /* Interestingly, in Windows, if cRDN is 0, rgRGN may not be NULL. My + * decoder works the same way, so only test the count. + */ + if (buf) + { + ok(bufSize == sizeof(CERT_NAME_INFO), + "Expected bufSize %d, got %ld\n", sizeof(CERT_NAME_INFO), bufSize); + ok(((CERT_NAME_INFO *)buf)->cRDN == 0, + "Expected 0 RDNs in empty info, got %ld\n", + ((CERT_NAME_INFO *)buf)->cRDN); + LocalFree(buf); + } + /* test empty RDN */ + bufSize = 0; + ret = CryptDecodeObjectEx(dwEncoding, X509_NAME, emptyRDNs, + emptyRDNs[1] + 2, + CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_SHARE_OID_STRING_FLAG, NULL, + (BYTE *)&buf, &bufSize); + ok(ret, "CryptDecodeObjectEx failed: %08lx\n", GetLastError()); + if (buf) + { + CERT_NAME_INFO *info = (CERT_NAME_INFO *)buf; + + ok(bufSize == sizeof(CERT_NAME_INFO) + sizeof(CERT_RDN) && + info->cRDN == 1 && info->rgRDN && info->rgRDN[0].cRDNAttr == 0, + "Got unexpected value for empty RDN\n"); + LocalFree(buf); + } + /* test two RDN attrs */ + bufSize = 0; + ret = CryptDecodeObjectEx(dwEncoding, X509_NAME, twoRDNs, + twoRDNs[1] + 2, + CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_SHARE_OID_STRING_FLAG, NULL, + (BYTE *)&buf, &bufSize); + ok(ret, "CryptDecodeObjectEx failed: %08lx\n", GetLastError()); + if (buf) + { + CERT_RDN_ATTR attrs[] = { + { szOID_SUR_NAME, CERT_RDN_PRINTABLE_STRING, { sizeof(surName), + (BYTE *)surName } }, + { szOID_COMMON_NAME, CERT_RDN_PRINTABLE_STRING, { sizeof(commonName), + (BYTE *)commonName } }, + }; + + rdn.cRDNAttr = sizeof(attrs) / sizeof(attrs[0]); + rdn.rgRDNAttr = attrs; + compareNames((CERT_NAME_INFO *)buf, &info); + LocalFree(buf); + } +} + static void test_registerOIDFunction(void) { static const WCHAR bogusDll[] = { 'b','o','g','u','s','.','d','l','l',0 }; @@ -353,9 +640,18 @@ static void test_registerOIDFunction(void) START_TEST(encode) { - test_encodeint(); - test_decodeint(); - test_encodeFiletime(); - test_decodeFiletime(); + static const DWORD encodings[] = { X509_ASN_ENCODING, PKCS_7_ASN_ENCODING, + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING }; + DWORD i; + + for (i = 0; i < sizeof(encodings) / sizeof(encodings[0]); i++) + { + test_encodeint(encodings[i]); + test_decodeint(encodings[i]); + test_encodeFiletime(encodings[i]); + test_decodeFiletime(encodings[i]); + test_encodeName(encodings[i]); + test_decodeName(encodings[i]); + } test_registerOIDFunction(); }