1 /* $OpenBSD: cms.c,v 1.50 2024/11/27 15:19:26 tb 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 <err.h> 19 #include <stdint.h> 20 #include <stdlib.h> 21 #include <string.h> 22 #include <unistd.h> 23 24 #include <openssl/bio.h> 25 #include <openssl/cms.h> 26 27 #include "extern.h" 28 29 extern ASN1_OBJECT *cnt_type_oid; 30 extern ASN1_OBJECT *msg_dgst_oid; 31 extern ASN1_OBJECT *sign_time_oid; 32 33 static int 34 cms_extract_econtent(const char *fn, CMS_ContentInfo *cms, unsigned char **res, 35 size_t *rsz) 36 { 37 ASN1_OCTET_STRING **os = NULL; 38 39 /* Detached signature case: no eContent to extract, so do nothing. */ 40 if (res == NULL || rsz == NULL) 41 return 1; 42 43 if ((os = CMS_get0_content(cms)) == NULL || *os == NULL) { 44 warnx("%s: RFC 6488 section 2.1.4: " 45 "eContent: zero-length content", fn); 46 return 0; 47 } 48 49 /* 50 * Extract and duplicate the eContent. 51 * The CMS framework offers us no other way of easily managing 52 * this information; and since we're going to d2i it anyway, 53 * simply pass it as the desired underlying types. 54 */ 55 if ((*res = malloc((*os)->length)) == NULL) 56 err(1, NULL); 57 memcpy(*res, (*os)->data, (*os)->length); 58 *rsz = (*os)->length; 59 60 return 1; 61 } 62 63 static int 64 cms_get_signtime(const char *fn, X509_ATTRIBUTE *attr, time_t *signtime) 65 { 66 const ASN1_TIME *at; 67 const char *time_str = "UTCtime"; 68 int time_type = V_ASN1_UTCTIME; 69 70 *signtime = 0; 71 at = X509_ATTRIBUTE_get0_data(attr, 0, time_type, NULL); 72 if (at == NULL) { 73 time_str = "GeneralizedTime"; 74 time_type = V_ASN1_GENERALIZEDTIME; 75 at = X509_ATTRIBUTE_get0_data(attr, 0, time_type, NULL); 76 if (at == NULL) { 77 warnx("%s: CMS signing-time issue", fn); 78 return 0; 79 } 80 warnx("%s: GeneralizedTime instead of UTCtime", fn); 81 } 82 83 if (!x509_get_time(at, signtime)) { 84 warnx("%s: failed to convert %s", fn, time_str); 85 return 0; 86 } 87 88 return 1; 89 } 90 91 static int 92 cms_parse_validate_internal(X509 **xp, const char *fn, const unsigned char *der, 93 size_t len, const ASN1_OBJECT *oid, BIO *bio, unsigned char **res, 94 size_t *rsz, time_t *signtime) 95 { 96 const unsigned char *oder; 97 char buf[128], obuf[128]; 98 const ASN1_OBJECT *obj, *octype; 99 ASN1_OCTET_STRING *kid = NULL; 100 CMS_ContentInfo *cms; 101 long version; 102 STACK_OF(X509) *certs = NULL; 103 STACK_OF(X509_CRL) *crls = NULL; 104 STACK_OF(CMS_SignerInfo) *sinfos; 105 CMS_SignerInfo *si; 106 EVP_PKEY *pkey; 107 X509_ALGOR *pdig, *psig; 108 int i, nattrs, nid; 109 int has_ct = 0, has_md = 0, has_st = 0; 110 time_t notafter; 111 int rc = 0; 112 113 *xp = NULL; 114 if (rsz != NULL) 115 *rsz = 0; 116 *signtime = 0; 117 118 /* just fail for empty buffers, the warning was printed elsewhere */ 119 if (der == NULL) 120 return 0; 121 122 oder = der; 123 if ((cms = d2i_CMS_ContentInfo(NULL, &der, len)) == NULL) { 124 warnx("%s: RFC 6488: failed CMS parse", fn); 125 goto out; 126 } 127 if (der != oder + len) { 128 warnx("%s: %td bytes trailing garbage", fn, oder + len - der); 129 goto out; 130 } 131 132 /* 133 * The CMS is self-signed with a signing certificate. 134 * Verify that the self-signage is correct. 135 */ 136 if (!CMS_verify(cms, NULL, NULL, bio, NULL, 137 CMS_NO_SIGNER_CERT_VERIFY)) { 138 warnx("%s: CMS verification error", fn); 139 goto out; 140 } 141 142 /* RFC 6488 section 3 verify the CMS */ 143 144 /* Should only return NULL if cms is not of type SignedData. */ 145 if ((sinfos = CMS_get0_SignerInfos(cms)) == NULL) { 146 if ((obj = CMS_get0_type(cms)) == NULL) { 147 warnx("%s: RFC 6488: missing content-type", fn); 148 goto out; 149 } 150 OBJ_obj2txt(buf, sizeof(buf), obj, 1); 151 warnx("%s: RFC 6488: no signerInfo in CMS object of type %s", 152 fn, buf); 153 goto out; 154 } 155 if (sk_CMS_SignerInfo_num(sinfos) != 1) { 156 warnx("%s: RFC 6488: CMS has multiple signerInfos", fn); 157 goto out; 158 } 159 si = sk_CMS_SignerInfo_value(sinfos, 0); 160 161 if (!CMS_get_version(cms, &version)) { 162 warnx("%s: Failed to retrieve SignedData version", fn); 163 goto out; 164 } 165 if (version != 3) { 166 warnx("%s: SignedData version %ld != 3", fn, version); 167 goto out; 168 } 169 if (!CMS_SignerInfo_get_version(si, &version)) { 170 warnx("%s: Failed to retrieve SignerInfo version", fn); 171 goto out; 172 } 173 if (version != 3) { 174 warnx("%s: SignerInfo version %ld != 3", fn, version); 175 goto out; 176 } 177 178 nattrs = CMS_signed_get_attr_count(si); 179 if (nattrs <= 0) { 180 warnx("%s: RFC 6488: error extracting signedAttrs", fn); 181 goto out; 182 } 183 for (i = 0; i < nattrs; i++) { 184 X509_ATTRIBUTE *attr; 185 186 attr = CMS_signed_get_attr(si, i); 187 if (attr == NULL || X509_ATTRIBUTE_count(attr) != 1) { 188 warnx("%s: RFC 6488: bad signed attribute encoding", 189 fn); 190 goto out; 191 } 192 193 obj = X509_ATTRIBUTE_get0_object(attr); 194 if (obj == NULL) { 195 warnx("%s: RFC 6488: bad signed attribute", fn); 196 goto out; 197 } 198 if (OBJ_cmp(obj, cnt_type_oid) == 0) { 199 if (has_ct++ != 0) { 200 warnx("%s: RFC 6488: duplicate " 201 "signed attribute", fn); 202 goto out; 203 } 204 } else if (OBJ_cmp(obj, msg_dgst_oid) == 0) { 205 if (has_md++ != 0) { 206 warnx("%s: RFC 6488: duplicate " 207 "signed attribute", fn); 208 goto out; 209 } 210 } else if (OBJ_cmp(obj, sign_time_oid) == 0) { 211 if (has_st++ != 0) { 212 warnx("%s: RFC 6488: duplicate " 213 "signed attribute", fn); 214 goto out; 215 } 216 if (!cms_get_signtime(fn, attr, signtime)) 217 goto out; 218 } else { 219 OBJ_obj2txt(buf, sizeof(buf), obj, 1); 220 warnx("%s: RFC 6488: " 221 "CMS has unexpected signed attribute %s", 222 fn, buf); 223 goto out; 224 } 225 } 226 227 if (!has_ct || !has_md) { 228 /* RFC 9589, section 4 */ 229 warnx("%s: RFC 6488: CMS missing required " 230 "signed attribute", fn); 231 goto out; 232 } 233 234 if (!has_st) { 235 /* RFC 9589, section 4 */ 236 warnx("%s: missing CMS signing-time attribute", fn); 237 goto out; 238 } 239 240 if (CMS_unsigned_get_attr_count(si) != -1) { 241 warnx("%s: RFC 6488: CMS has unsignedAttrs", fn); 242 goto out; 243 } 244 245 /* Check digest and signature algorithms (RFC 7935) */ 246 CMS_SignerInfo_get0_algs(si, &pkey, NULL, &pdig, &psig); 247 if (!valid_ca_pkey(fn, pkey)) 248 goto out; 249 250 X509_ALGOR_get0(&obj, NULL, NULL, pdig); 251 nid = OBJ_obj2nid(obj); 252 if (nid != NID_sha256) { 253 warnx("%s: RFC 6488: wrong digest %s, want %s", fn, 254 nid2str(nid), LN_sha256); 255 goto out; 256 } 257 X509_ALGOR_get0(&obj, NULL, NULL, psig); 258 nid = OBJ_obj2nid(obj); 259 /* RFC7935 last paragraph of section 2 specifies the allowed psig */ 260 if (experimental && nid == NID_ecdsa_with_SHA256) { 261 if (verbose) 262 warnx("%s: P-256 support is experimental", fn); 263 } else if (nid != NID_rsaEncryption && 264 nid != NID_sha256WithRSAEncryption) { 265 warnx("%s: RFC 6488: wrong signature algorithm %s, want %s", 266 fn, nid2str(nid), LN_rsaEncryption); 267 goto out; 268 } 269 270 /* RFC 6488 section 2.1.3.1: check the object's eContentType. */ 271 272 obj = CMS_get0_eContentType(cms); 273 if (obj == NULL) { 274 warnx("%s: RFC 6488 section 2.1.3.1: eContentType: " 275 "OID object is NULL", fn); 276 goto out; 277 } 278 if (OBJ_cmp(obj, oid) != 0) { 279 OBJ_obj2txt(buf, sizeof(buf), obj, 1); 280 OBJ_obj2txt(obuf, sizeof(obuf), oid, 1); 281 warnx("%s: RFC 6488 section 2.1.3.1: eContentType: " 282 "unknown OID: %s, want %s", fn, buf, obuf); 283 goto out; 284 } 285 286 /* Compare content-type with eContentType */ 287 octype = CMS_signed_get0_data_by_OBJ(si, cnt_type_oid, 288 -3, V_ASN1_OBJECT); 289 /* 290 * Since lastpos == -3, octype can be NULL for 4 reasons: 291 * 1. requested attribute OID is missing 292 * 2. signedAttrs contains multiple attributes with requested OID 293 * 3. attribute with requested OID has multiple values (malformed) 294 * 4. X509_ATTRIBUTE_get0_data() returned NULL. This is also malformed, 295 * but libcrypto will create, sign, and verify such objects. 296 * Reasons 1 and 2 are excluded because has_ct == 1. We don't know which 297 * one of 3 or 4 we hit. Doesn't matter, drop the garbage on the floor. 298 */ 299 if (octype == NULL) { 300 warnx("%s: RFC 6488, section 2.1.6.4.1: malformed value " 301 "for content-type attribute", fn); 302 goto out; 303 } 304 if (OBJ_cmp(obj, octype) != 0) { 305 OBJ_obj2txt(buf, sizeof(buf), obj, 1); 306 OBJ_obj2txt(obuf, sizeof(obuf), octype, 1); 307 warnx("%s: RFC 6488: eContentType does not match Content-Type " 308 "OID: %s, want %s", fn, buf, obuf); 309 goto out; 310 } 311 312 /* 313 * Check that there are no CRLS in this CMS message. 314 * XXX - can only error check for OpenSSL >= 3.4. 315 */ 316 crls = CMS_get1_crls(cms); 317 if (crls != NULL && sk_X509_CRL_num(crls) != 0) { 318 warnx("%s: RFC 6488: CMS has CRLs", fn); 319 goto out; 320 } 321 322 /* 323 * The self-signing certificate is further signed by the input 324 * signing authority according to RFC 6488, 2.1.4. 325 * We extract that certificate now for later verification. 326 */ 327 328 certs = CMS_get0_signers(cms); 329 if (certs == NULL || sk_X509_num(certs) != 1) { 330 warnx("%s: RFC 6488 section 2.1.4: eContent: " 331 "want 1 signer, have %d", fn, sk_X509_num(certs)); 332 goto out; 333 } 334 *xp = sk_X509_value(certs, 0); 335 if (!X509_up_ref(*xp)) { 336 *xp = NULL; 337 goto out; 338 } 339 340 if (!x509_cache_extensions(*xp, fn)) 341 goto out; 342 343 if (!x509_get_notafter(*xp, fn, ¬after)) 344 goto out; 345 if (*signtime > notafter) 346 warnx("%s: dating issue: CMS signing-time after X.509 notAfter", 347 fn); 348 349 if (CMS_SignerInfo_get0_signer_id(si, &kid, NULL, NULL) != 1 || 350 kid == NULL) { 351 warnx("%s: RFC 6488: could not extract SKI from SID", fn); 352 goto out; 353 } 354 if (CMS_SignerInfo_cert_cmp(si, *xp) != 0) { 355 warnx("%s: RFC 6488: wrong cert referenced by SignerInfo", fn); 356 goto out; 357 } 358 359 if (!cms_extract_econtent(fn, cms, res, rsz)) 360 goto out; 361 362 rc = 1; 363 out: 364 if (rc == 0) { 365 X509_free(*xp); 366 *xp = NULL; 367 } 368 sk_X509_CRL_pop_free(crls, X509_CRL_free); 369 sk_X509_free(certs); 370 CMS_ContentInfo_free(cms); 371 return rc; 372 } 373 374 /* 375 * Parse and validate a self-signed CMS message. 376 * Conforms to RFC 6488. 377 * The eContentType of the message must be an oid object. 378 * Return the eContent as a string and set "rsz" to be its length. 379 */ 380 unsigned char * 381 cms_parse_validate(X509 **xp, const char *fn, const unsigned char *der, 382 size_t derlen, const ASN1_OBJECT *oid, size_t *rsz, time_t *st) 383 { 384 unsigned char *res = NULL; 385 386 if (!cms_parse_validate_internal(xp, fn, der, derlen, oid, NULL, &res, 387 rsz, st)) 388 return NULL; 389 390 return res; 391 } 392 393 /* 394 * Parse and validate a detached CMS signature. 395 * bio must contain the original message, der must contain the CMS. 396 * Return the 1 on success, 0 on failure. 397 */ 398 int 399 cms_parse_validate_detached(X509 **xp, const char *fn, const unsigned char *der, 400 size_t derlen, const ASN1_OBJECT *oid, BIO *bio, time_t *st) 401 { 402 return cms_parse_validate_internal(xp, fn, der, derlen, oid, bio, NULL, 403 NULL, st); 404 } 405