1 /* $OpenBSD: cms.c,v 1.26 2022/12/28 21:30:18 jmc Exp $ */ 2 /* 3 * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18 #include <assert.h> 19 #include <err.h> 20 #include <stdint.h> 21 #include <stdlib.h> 22 #include <string.h> 23 #include <unistd.h> 24 25 #include <openssl/bio.h> 26 #include <openssl/cms.h> 27 28 #include "extern.h" 29 30 extern ASN1_OBJECT *cnt_type_oid; 31 extern ASN1_OBJECT *msg_dgst_oid; 32 extern ASN1_OBJECT *sign_time_oid; 33 extern ASN1_OBJECT *bin_sign_time_oid; 34 35 static int 36 cms_extract_econtent(const char *fn, CMS_ContentInfo *cms, unsigned char **res, 37 size_t *rsz) 38 { 39 ASN1_OCTET_STRING **os = NULL; 40 41 /* Detached signature case: no eContent to extract, so do nothing. */ 42 if (res == NULL || rsz == NULL) 43 return 1; 44 45 if ((os = CMS_get0_content(cms)) == NULL || *os == NULL) { 46 warnx("%s: RFC 6488 section 2.1.4: " 47 "eContent: zero-length content", fn); 48 return 0; 49 } 50 51 /* 52 * Extract and duplicate the eContent. 53 * The CMS framework offers us no other way of easily managing 54 * this information; and since we're going to d2i it anyway, 55 * simply pass it as the desired underlying types. 56 */ 57 if ((*res = malloc((*os)->length)) == NULL) 58 err(1, NULL); 59 memcpy(*res, (*os)->data, (*os)->length); 60 *rsz = (*os)->length; 61 62 return 1; 63 } 64 65 static int 66 cms_parse_validate_internal(X509 **xp, const char *fn, const unsigned char *der, 67 size_t derlen, const ASN1_OBJECT *oid, BIO *bio, unsigned char **res, 68 size_t *rsz) 69 { 70 char buf[128], obuf[128]; 71 const ASN1_OBJECT *obj, *octype; 72 ASN1_OCTET_STRING *kid = NULL; 73 CMS_ContentInfo *cms; 74 STACK_OF(X509) *certs = NULL; 75 STACK_OF(X509_CRL) *crls; 76 STACK_OF(CMS_SignerInfo) *sinfos; 77 CMS_SignerInfo *si; 78 X509_ALGOR *pdig, *psig; 79 int i, nattrs, nid; 80 int has_ct = 0, has_md = 0, has_st = 0, 81 has_bst = 0; 82 int rc = 0; 83 84 *xp = NULL; 85 if (rsz != NULL) 86 *rsz = 0; 87 88 /* just fail for empty buffers, the warning was printed elsewhere */ 89 if (der == NULL) 90 return 0; 91 92 if ((cms = d2i_CMS_ContentInfo(NULL, &der, derlen)) == NULL) { 93 cryptowarnx("%s: RFC 6488: failed CMS parse", fn); 94 goto out; 95 } 96 97 /* 98 * The CMS is self-signed with a signing certificate. 99 * Verify that the self-signage is correct. 100 */ 101 if (!CMS_verify(cms, NULL, NULL, bio, NULL, 102 CMS_NO_SIGNER_CERT_VERIFY)) { 103 cryptowarnx("%s: CMS verification error", fn); 104 goto out; 105 } 106 107 /* RFC 6488 section 3 verify the CMS */ 108 /* the version of SignedData and SignerInfos can't be verified */ 109 110 sinfos = CMS_get0_SignerInfos(cms); 111 assert(sinfos != NULL); 112 if (sk_CMS_SignerInfo_num(sinfos) != 1) { 113 cryptowarnx("%s: RFC 6488: CMS has multiple signerInfos", fn); 114 goto out; 115 } 116 si = sk_CMS_SignerInfo_value(sinfos, 0); 117 118 nattrs = CMS_signed_get_attr_count(si); 119 if (nattrs <= 0) { 120 cryptowarnx("%s: RFC 6488: error extracting signedAttrs", fn); 121 goto out; 122 } 123 for (i = 0; i < nattrs; i++) { 124 X509_ATTRIBUTE *attr; 125 126 attr = CMS_signed_get_attr(si, i); 127 if (attr == NULL || X509_ATTRIBUTE_count(attr) != 1) { 128 cryptowarnx("%s: RFC 6488: " 129 "bad signed attribute encoding", fn); 130 goto out; 131 } 132 133 obj = X509_ATTRIBUTE_get0_object(attr); 134 if (obj == NULL) { 135 cryptowarnx("%s: RFC 6488: bad signed attribute", fn); 136 goto out; 137 } 138 if (OBJ_cmp(obj, cnt_type_oid) == 0) { 139 if (has_ct++ != 0) { 140 cryptowarnx("%s: RFC 6488: duplicate " 141 "signed attribute", fn); 142 goto out; 143 } 144 } else if (OBJ_cmp(obj, msg_dgst_oid) == 0) { 145 if (has_md++ != 0) { 146 cryptowarnx("%s: RFC 6488: duplicate " 147 "signed attribute", fn); 148 goto out; 149 } 150 } else if (OBJ_cmp(obj, sign_time_oid) == 0) { 151 if (has_st++ != 0) { 152 cryptowarnx("%s: RFC 6488: duplicate " 153 "signed attribute", fn); 154 goto out; 155 } 156 } else if (OBJ_cmp(obj, bin_sign_time_oid) == 0) { 157 if (has_bst++ != 0) { 158 cryptowarnx("%s: RFC 6488: duplicate " 159 "signed attribute", fn); 160 goto out; 161 } 162 } else { 163 OBJ_obj2txt(buf, sizeof(buf), obj, 1); 164 cryptowarnx("%s: RFC 6488: " 165 "CMS has unexpected signed attribute %s", 166 fn, buf); 167 goto out; 168 } 169 } 170 if (!has_ct || !has_md) { 171 cryptowarnx("%s: RFC 6488: CMS missing required " 172 "signed attribute", fn); 173 goto out; 174 } 175 if (CMS_unsigned_get_attr_count(si) != -1) { 176 cryptowarnx("%s: RFC 6488: CMS has unsignedAttrs", fn); 177 goto out; 178 } 179 180 /* Check digest and signature algorithms */ 181 CMS_SignerInfo_get0_algs(si, NULL, NULL, &pdig, &psig); 182 X509_ALGOR_get0(&obj, NULL, NULL, pdig); 183 nid = OBJ_obj2nid(obj); 184 if (nid != NID_sha256) { 185 warnx("%s: RFC 6488: wrong digest %s, want %s", fn, 186 OBJ_nid2ln(nid), OBJ_nid2ln(NID_sha256)); 187 goto out; 188 } 189 X509_ALGOR_get0(&obj, NULL, NULL, psig); 190 nid = OBJ_obj2nid(obj); 191 /* RFC7395 last paragraph of section 2 specifies the allowed psig */ 192 if (nid != NID_rsaEncryption && nid != NID_sha256WithRSAEncryption) { 193 warnx("%s: RFC 6488: wrong signature algorithm %s, want %s", 194 fn, OBJ_nid2ln(nid), OBJ_nid2ln(NID_rsaEncryption)); 195 goto out; 196 } 197 198 /* RFC 6488 section 2.1.3.1: check the object's eContentType. */ 199 200 obj = CMS_get0_eContentType(cms); 201 if (obj == NULL) { 202 warnx("%s: RFC 6488 section 2.1.3.1: eContentType: " 203 "OID object is NULL", fn); 204 goto out; 205 } 206 if (OBJ_cmp(obj, oid) != 0) { 207 OBJ_obj2txt(buf, sizeof(buf), obj, 1); 208 OBJ_obj2txt(obuf, sizeof(obuf), oid, 1); 209 warnx("%s: RFC 6488 section 2.1.3.1: eContentType: " 210 "unknown OID: %s, want %s", fn, buf, obuf); 211 goto out; 212 } 213 214 /* Compare content-type with eContentType */ 215 octype = CMS_signed_get0_data_by_OBJ(si, cnt_type_oid, 216 -3, V_ASN1_OBJECT); 217 assert(octype != NULL); 218 if (OBJ_cmp(obj, octype) != 0) { 219 OBJ_obj2txt(buf, sizeof(buf), obj, 1); 220 OBJ_obj2txt(obuf, sizeof(obuf), oid, 1); 221 warnx("%s: RFC 6488: eContentType does not match Content-Type " 222 "OID: %s, want %s", fn, buf, obuf); 223 goto out; 224 } 225 226 /* 227 * Check that there are no CRLS in this CMS message. 228 */ 229 crls = CMS_get1_crls(cms); 230 if (crls != NULL) { 231 sk_X509_CRL_pop_free(crls, X509_CRL_free); 232 cryptowarnx("%s: RFC 6488: CMS has CRLs", fn); 233 goto out; 234 } 235 236 /* 237 * The self-signing certificate is further signed by the input 238 * signing authority according to RFC 6488, 2.1.4. 239 * We extract that certificate now for later verification. 240 */ 241 242 certs = CMS_get0_signers(cms); 243 if (certs == NULL || sk_X509_num(certs) != 1) { 244 warnx("%s: RFC 6488 section 2.1.4: eContent: " 245 "want 1 signer, have %d", fn, sk_X509_num(certs)); 246 goto out; 247 } 248 *xp = sk_X509_value(certs, 0); 249 if (!X509_up_ref(*xp)) { 250 *xp = NULL; 251 goto out; 252 } 253 254 /* Cache X509v3 extensions, see X509_check_ca(3). */ 255 if (X509_check_purpose(*xp, -1, -1) <= 0) { 256 cryptowarnx("%s: could not cache X509v3 extensions", fn); 257 goto out; 258 } 259 260 if (CMS_SignerInfo_get0_signer_id(si, &kid, NULL, NULL) != 1 || 261 kid == NULL) { 262 warnx("%s: RFC 6488: could not extract SKI from SID", fn); 263 goto out; 264 } 265 if (CMS_SignerInfo_cert_cmp(si, *xp) != 0) { 266 warnx("%s: RFC 6488: wrong cert referenced by SignerInfo", fn); 267 goto out; 268 } 269 270 if (!cms_extract_econtent(fn, cms, res, rsz)) 271 goto out; 272 273 rc = 1; 274 out: 275 if (rc == 0) { 276 X509_free(*xp); 277 *xp = NULL; 278 } 279 sk_X509_free(certs); 280 CMS_ContentInfo_free(cms); 281 return rc; 282 } 283 284 /* 285 * Parse and validate a self-signed CMS message. 286 * Conforms to RFC 6488. 287 * The eContentType of the message must be an oid object. 288 * Return the eContent as a string and set "rsz" to be its length. 289 */ 290 unsigned char * 291 cms_parse_validate(X509 **xp, const char *fn, const unsigned char *der, 292 size_t derlen, const ASN1_OBJECT *oid, size_t *rsz) 293 { 294 unsigned char *res = NULL; 295 296 if (!cms_parse_validate_internal(xp, fn, der, derlen, oid, NULL, &res, 297 rsz)) 298 return NULL; 299 300 return res; 301 } 302 303 /* 304 * Parse and validate a detached CMS signature. 305 * bio must contain the original message, der must contain the CMS. 306 * Return the 1 on success, 0 on failure. 307 */ 308 int 309 cms_parse_validate_detached(X509 **xp, const char *fn, const unsigned char *der, 310 size_t derlen, const ASN1_OBJECT *oid, BIO *bio) 311 { 312 return cms_parse_validate_internal(xp, fn, der, derlen, oid, bio, NULL, 313 NULL); 314 } 315