#include <assert.h>
#include <err.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <openssl/cms.h>
#include "extern.h"
static int
cms_extract_econtent(const char *fn, CMS_ContentInfo *cms, unsigned char **res,
size_t *rsz)
{
ASN1_OCTET_STRING **os = NULL;
if ((os = CMS_get0_content(cms)) == NULL || *os == NULL) {
warnx("%s: RFC 6488 section 2.1.4: "
"eContent: zero-length content", fn);
return 0;
}
if ((*rsz = ASN1_STRING_length(*os)) == 0) {
warnx("%s: RFC 6488 section 2.1.4: "
"eContent: zero-length content", fn);
return 0;
}
if (*rsz > MAX_FILE_SIZE) {
warnx("%s: overlong eContent of length %zu", fn, *rsz);
return 0;
}
if ((*res = malloc(*rsz)) == NULL)
err(1, NULL);
memcpy(*res, ASN1_STRING_get0_data(*os), *rsz);
return 1;
}
static int
cms_get_signtime(const char *fn, X509_ATTRIBUTE *attr, time_t *signtime)
{
const ASN1_TIME *at;
const char *time_str = "UTCTime";
int time_type = V_ASN1_UTCTIME;
*signtime = 0;
at = X509_ATTRIBUTE_get0_data(attr, 0, time_type, NULL);
if (at == NULL) {
time_str = "GeneralizedTime";
time_type = V_ASN1_GENERALIZEDTIME;
at = X509_ATTRIBUTE_get0_data(attr, 0, time_type, NULL);
if (at == NULL) {
warnx("%s: CMS signing-time issue", fn);
return 0;
}
warnx("%s: GeneralizedTime instead of UTCTime", fn);
}
if (!x509_get_time(at, signtime)) {
warnx("%s: failed to convert %s", fn, time_str);
return 0;
}
return 1;
}
static int
cms_SignerInfo_check_attributes(const char *fn, const CMS_SignerInfo *si,
time_t *signtime)
{
char buf[128];
const ASN1_OBJECT *obj;
int i, nattrs;
int has_ct = 0, has_md = 0, has_st = 0;
*signtime = 0;
nattrs = CMS_signed_get_attr_count(si);
if (nattrs <= 0) {
warnx("%s: RFC 6488: error extracting signedAttrs", fn);
return 0;
}
for (i = 0; i < nattrs; i++) {
X509_ATTRIBUTE *attr;
attr = CMS_signed_get_attr(si, i);
if (attr == NULL || X509_ATTRIBUTE_count(attr) != 1) {
warnx("%s: RFC 6488: bad signed attribute encoding",
fn);
return 0;
}
obj = X509_ATTRIBUTE_get0_object(attr);
if (obj == NULL) {
warnx("%s: RFC 6488: bad signed attribute", fn);
return 0;
}
if (OBJ_cmp(obj, cnt_type_oid) == 0) {
if (has_ct++ != 0) {
warnx("%s: RFC 6488: duplicate "
"signed attribute", fn);
return 0;
}
} else if (OBJ_cmp(obj, msg_dgst_oid) == 0) {
if (has_md++ != 0) {
warnx("%s: RFC 6488: duplicate "
"signed attribute", fn);
return 0;
}
} else if (OBJ_cmp(obj, sign_time_oid) == 0) {
if (has_st++ != 0) {
warnx("%s: RFC 6488: duplicate "
"signed attribute", fn);
return 0;
}
if (!cms_get_signtime(fn, attr, signtime))
return 0;
} else {
OBJ_obj2txt(buf, sizeof(buf), obj, 1);
warnx("%s: RFC 6488: "
"CMS has unexpected signed attribute %s",
fn, buf);
return 0;
}
}
if (!has_ct || !has_md) {
warnx("%s: RFC 6488: CMS missing required "
"signed attribute", fn);
return 0;
}
if (!has_st) {
warnx("%s: missing CMS signing-time attribute", fn);
return 0;
}
if (CMS_unsigned_get_attr_count(si) != -1) {
warnx("%s: RFC 6488: CMS has unsignedAttrs", fn);
return 0;
}
return 1;
}
static int
cms_parse_validate_internal(struct cert **out_cert, const char *fn, int talid,
const unsigned char *der, size_t len, const ASN1_OBJECT *oid,
unsigned char **res, size_t *rsz, time_t *signtime)
{
struct cert *cert = NULL;
const unsigned char *oder;
char buf[128], obuf[128];
const ASN1_OBJECT *obj, *octype;
ASN1_OCTET_STRING *kid = NULL;
CMS_ContentInfo *cms;
long version;
STACK_OF(X509) *certs = NULL;
STACK_OF(X509_CRL) *crls = NULL;
STACK_OF(CMS_SignerInfo) *sinfos;
CMS_SignerInfo *si;
X509_ALGOR *pdig, *psig;
int nid;
int rc = 0;
assert(*out_cert == NULL);
if (rsz != NULL)
*rsz = 0;
*signtime = 0;
if (der == NULL)
return 0;
oder = der;
if ((cms = d2i_CMS_ContentInfo(NULL, &der, len)) == NULL) {
warnx("%s: RFC 6488: failed CMS parse", fn);
goto out;
}
if (der != oder + len) {
warnx("%s: %td bytes trailing garbage", fn, oder + len - der);
goto out;
}
if (!CMS_verify(cms, NULL, NULL, NULL, NULL,
CMS_NO_SIGNER_CERT_VERIFY)) {
warnx("%s: CMS verification error", fn);
goto out;
}
if ((sinfos = CMS_get0_SignerInfos(cms)) == NULL) {
if ((obj = CMS_get0_type(cms)) == NULL) {
warnx("%s: RFC 6488: missing content-type", fn);
goto out;
}
OBJ_obj2txt(buf, sizeof(buf), obj, 1);
warnx("%s: RFC 6488: no signerInfo in CMS object of type %s",
fn, buf);
goto out;
}
if (sk_CMS_SignerInfo_num(sinfos) != 1) {
warnx("%s: RFC 6488: CMS has multiple signerInfos", fn);
goto out;
}
si = sk_CMS_SignerInfo_value(sinfos, 0);
if (!CMS_get_version(cms, &version)) {
warnx("%s: Failed to retrieve SignedData version", fn);
goto out;
}
if (version != 3) {
warnx("%s: SignedData version %ld != 3", fn, version);
goto out;
}
if (!CMS_SignerInfo_get_version(si, &version)) {
warnx("%s: Failed to retrieve SignerInfo version", fn);
goto out;
}
if (version != 3) {
warnx("%s: SignerInfo version %ld != 3", fn, version);
goto out;
}
if (!cms_SignerInfo_check_attributes(fn, si, signtime))
goto out;
CMS_SignerInfo_get0_algs(si, NULL, NULL, &pdig, &psig);
X509_ALGOR_get0(&obj, NULL, NULL, pdig);
nid = OBJ_obj2nid(obj);
if (nid != NID_sha256) {
warnx("%s: RFC 6488: wrong digest %s, want %s", fn,
nid2str(nid), LN_sha256);
goto out;
}
X509_ALGOR_get0(&obj, NULL, NULL, psig);
nid = OBJ_obj2nid(obj);
if (experimental && nid == NID_ecdsa_with_SHA256) {
if (verbose)
warnx("%s: P-256 support is experimental", fn);
} else if (nid != NID_rsaEncryption &&
nid != NID_sha256WithRSAEncryption) {
warnx("%s: RFC 6488: wrong signature algorithm %s, want %s",
fn, nid2str(nid), LN_rsaEncryption);
goto out;
}
obj = CMS_get0_eContentType(cms);
if (obj == NULL) {
warnx("%s: RFC 6488 section 2.1.3.1: eContentType: "
"OID object is NULL", fn);
goto out;
}
if (OBJ_cmp(obj, oid) != 0) {
OBJ_obj2txt(buf, sizeof(buf), obj, 1);
OBJ_obj2txt(obuf, sizeof(obuf), oid, 1);
warnx("%s: RFC 6488 section 2.1.3.1: eContentType: "
"unknown OID: %s, want %s", fn, buf, obuf);
goto out;
}
octype = CMS_signed_get0_data_by_OBJ(si, cnt_type_oid,
-3, V_ASN1_OBJECT);
if (octype == NULL) {
warnx("%s: RFC 6488, section 2.1.6.4.1: malformed value "
"for content-type attribute", fn);
goto out;
}
if (OBJ_cmp(obj, octype) != 0) {
OBJ_obj2txt(buf, sizeof(buf), obj, 1);
OBJ_obj2txt(obuf, sizeof(obuf), octype, 1);
warnx("%s: RFC 6488: eContentType does not match Content-Type "
"OID: %s, want %s", fn, buf, obuf);
goto out;
}
crls = CMS_get1_crls(cms);
if (crls != NULL && sk_X509_CRL_num(crls) != 0) {
warnx("%s: RFC 6488: CMS has CRLs", fn);
goto out;
}
certs = CMS_get0_signers(cms);
if (certs == NULL || sk_X509_num(certs) != 1) {
warnx("%s: RFC 6488 section 2.1.4: eContent: "
"want 1 signer, have %d", fn, sk_X509_num(certs));
goto out;
}
cert = cert_parse_ee_cert(fn, talid, sk_X509_value(certs, 0));
if (cert == NULL)
goto out;
if (*signtime > cert->notafter)
warnx("%s: dating issue: CMS signing-time after X.509 notAfter",
fn);
if (CMS_SignerInfo_get0_signer_id(si, &kid, NULL, NULL) != 1 ||
kid == NULL) {
warnx("%s: RFC 6488: could not extract SKI from SID", fn);
goto out;
}
if (CMS_SignerInfo_cert_cmp(si, cert->x509) != 0) {
warnx("%s: RFC 6488: wrong cert referenced by SignerInfo", fn);
goto out;
}
if (!cms_extract_econtent(fn, cms, res, rsz))
goto out;
*out_cert = cert;
cert = NULL;
rc = 1;
out:
cert_free(cert);
sk_X509_CRL_pop_free(crls, X509_CRL_free);
sk_X509_free(certs);
CMS_ContentInfo_free(cms);
return rc;
}
unsigned char *
cms_parse_validate(struct cert **out_cert, const char *fn, int talid,
const unsigned char *der, size_t derlen, const ASN1_OBJECT *oid,
size_t *rsz, time_t *st)
{
unsigned char *res = NULL;
if (!cms_parse_validate_internal(out_cert, fn, talid, der, derlen, oid,
&res, rsz, st))
return NULL;
return res;
}