1 /* $OpenBSD: cms.c,v 1.40 2023/10/19 17:05:54 job 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_get_signtime(const char *fn, X509_ATTRIBUTE *attr, time_t *signtime) 67 { 68 const ASN1_TIME *at; 69 const char *time_str = "UTCtime"; 70 int time_type = V_ASN1_UTCTIME; 71 72 *signtime = 0; 73 at = X509_ATTRIBUTE_get0_data(attr, 0, time_type, NULL); 74 if (at == NULL) { 75 time_str = "GeneralizedTime"; 76 time_type = V_ASN1_GENERALIZEDTIME; 77 at = X509_ATTRIBUTE_get0_data(attr, 0, time_type, NULL); 78 if (at == NULL) { 79 warnx("%s: CMS signing-time issue", fn); 80 return 0; 81 } 82 warnx("%s: GeneralizedTime instead of UTCtime", fn); 83 } 84 85 if (!x509_get_time(at, signtime)) { 86 warnx("%s: failed to convert %s", fn, time_str); 87 return 0; 88 } 89 90 return 1; 91 } 92 93 static int 94 cms_parse_validate_internal(X509 **xp, const char *fn, const unsigned char *der, 95 size_t len, const ASN1_OBJECT *oid, BIO *bio, unsigned char **res, 96 size_t *rsz, time_t *signtime) 97 { 98 const unsigned char *oder; 99 char buf[128], obuf[128]; 100 const ASN1_OBJECT *obj, *octype; 101 ASN1_OCTET_STRING *kid = NULL; 102 CMS_ContentInfo *cms; 103 long version; 104 STACK_OF(X509) *certs = NULL; 105 STACK_OF(X509_CRL) *crls; 106 STACK_OF(CMS_SignerInfo) *sinfos; 107 CMS_SignerInfo *si; 108 EVP_PKEY *pkey; 109 X509_ALGOR *pdig, *psig; 110 int i, nattrs, nid; 111 int has_ct = 0, has_md = 0, has_st = 0, 112 has_bst = 0; 113 time_t notafter; 114 int rc = 0; 115 116 *xp = NULL; 117 if (rsz != NULL) 118 *rsz = 0; 119 *signtime = 0; 120 121 /* just fail for empty buffers, the warning was printed elsewhere */ 122 if (der == NULL) 123 return 0; 124 125 oder = der; 126 if ((cms = d2i_CMS_ContentInfo(NULL, &der, len)) == NULL) { 127 warnx("%s: RFC 6488: failed CMS parse", fn); 128 goto out; 129 } 130 if (der != oder + len) { 131 warnx("%s: %td bytes trailing garbage", fn, oder + len - der); 132 goto out; 133 } 134 135 /* 136 * The CMS is self-signed with a signing certificate. 137 * Verify that the self-signage is correct. 138 */ 139 if (!CMS_verify(cms, NULL, NULL, bio, NULL, 140 CMS_NO_SIGNER_CERT_VERIFY)) { 141 warnx("%s: CMS verification error", fn); 142 goto out; 143 } 144 145 /* RFC 6488 section 3 verify the CMS */ 146 147 /* Should only return NULL if cms is not of type SignedData. */ 148 if ((sinfos = CMS_get0_SignerInfos(cms)) == NULL) { 149 if ((obj = CMS_get0_type(cms)) == NULL) { 150 warnx("%s: RFC 6488: missing content-type", fn); 151 goto out; 152 } 153 OBJ_obj2txt(buf, sizeof(buf), obj, 1); 154 warnx("%s: RFC 6488: no signerInfo in CMS object of type %s", 155 fn, buf); 156 goto out; 157 } 158 if (sk_CMS_SignerInfo_num(sinfos) != 1) { 159 warnx("%s: RFC 6488: CMS has multiple signerInfos", fn); 160 goto out; 161 } 162 si = sk_CMS_SignerInfo_value(sinfos, 0); 163 164 if (!CMS_get_version(cms, &version)) { 165 warnx("%s: Failed to retrieve SignedData version", fn); 166 goto out; 167 } 168 if (version != 3) { 169 warnx("%s: SignedData version %ld != 3", fn, version); 170 goto out; 171 } 172 if (!CMS_SignerInfo_get_version(si, &version)) { 173 warnx("%s: Failed to retrieve SignerInfo version", fn); 174 goto out; 175 } 176 if (version != 3) { 177 warnx("%s: SignerInfo version %ld != 3", fn, version); 178 goto out; 179 } 180 181 nattrs = CMS_signed_get_attr_count(si); 182 if (nattrs <= 0) { 183 warnx("%s: RFC 6488: error extracting signedAttrs", fn); 184 goto out; 185 } 186 for (i = 0; i < nattrs; i++) { 187 X509_ATTRIBUTE *attr; 188 189 attr = CMS_signed_get_attr(si, i); 190 if (attr == NULL || X509_ATTRIBUTE_count(attr) != 1) { 191 warnx("%s: RFC 6488: bad signed attribute encoding", 192 fn); 193 goto out; 194 } 195 196 obj = X509_ATTRIBUTE_get0_object(attr); 197 if (obj == NULL) { 198 warnx("%s: RFC 6488: bad signed attribute", fn); 199 goto out; 200 } 201 if (OBJ_cmp(obj, cnt_type_oid) == 0) { 202 if (has_ct++ != 0) { 203 warnx("%s: RFC 6488: duplicate " 204 "signed attribute", fn); 205 goto out; 206 } 207 } else if (OBJ_cmp(obj, msg_dgst_oid) == 0) { 208 if (has_md++ != 0) { 209 warnx("%s: RFC 6488: duplicate " 210 "signed attribute", fn); 211 goto out; 212 } 213 } else if (OBJ_cmp(obj, sign_time_oid) == 0) { 214 if (has_st++ != 0) { 215 warnx("%s: RFC 6488: duplicate " 216 "signed attribute", fn); 217 goto out; 218 } 219 if (!cms_get_signtime(fn, attr, signtime)) 220 goto out; 221 } else if (OBJ_cmp(obj, bin_sign_time_oid) == 0) { 222 if (has_bst++ != 0) { 223 warnx("%s: RFC 6488: duplicate " 224 "signed attribute", fn); 225 goto out; 226 } 227 } else { 228 OBJ_obj2txt(buf, sizeof(buf), obj, 1); 229 warnx("%s: RFC 6488: " 230 "CMS has unexpected signed attribute %s", 231 fn, buf); 232 goto out; 233 } 234 } 235 236 if (!has_ct || !has_md) { 237 warnx("%s: RFC 6488: CMS missing required " 238 "signed attribute", fn); 239 goto out; 240 } 241 242 if (has_bst) 243 warnx("%s: unsupported CMS signing-time attribute", fn); 244 245 if (!has_st) 246 warnx("%s: missing CMS signing-time attribute", fn); 247 248 if (CMS_unsigned_get_attr_count(si) != -1) { 249 warnx("%s: RFC 6488: CMS has unsignedAttrs", fn); 250 goto out; 251 } 252 253 /* Check digest and signature algorithms (RFC 7935) */ 254 CMS_SignerInfo_get0_algs(si, &pkey, NULL, &pdig, &psig); 255 if (!valid_ca_pkey(fn, pkey)) 256 goto out; 257 258 X509_ALGOR_get0(&obj, NULL, NULL, pdig); 259 nid = OBJ_obj2nid(obj); 260 if (nid != NID_sha256) { 261 warnx("%s: RFC 6488: wrong digest %s, want %s", fn, 262 OBJ_nid2ln(nid), OBJ_nid2ln(NID_sha256)); 263 goto out; 264 } 265 X509_ALGOR_get0(&obj, NULL, NULL, psig); 266 nid = OBJ_obj2nid(obj); 267 /* RFC7935 last paragraph of section 2 specifies the allowed psig */ 268 if (nid == NID_ecdsa_with_SHA256) { 269 if (verbose) 270 warn("%s: P-256 support is experimental", fn); 271 } else if (nid != NID_rsaEncryption && 272 nid != NID_sha256WithRSAEncryption) { 273 warnx("%s: RFC 6488: wrong signature algorithm %s, want %s", 274 fn, OBJ_nid2ln(nid), OBJ_nid2ln(NID_rsaEncryption)); 275 goto out; 276 } 277 278 /* RFC 6488 section 2.1.3.1: check the object's eContentType. */ 279 280 obj = CMS_get0_eContentType(cms); 281 if (obj == NULL) { 282 warnx("%s: RFC 6488 section 2.1.3.1: eContentType: " 283 "OID object is NULL", fn); 284 goto out; 285 } 286 if (OBJ_cmp(obj, oid) != 0) { 287 OBJ_obj2txt(buf, sizeof(buf), obj, 1); 288 OBJ_obj2txt(obuf, sizeof(obuf), oid, 1); 289 warnx("%s: RFC 6488 section 2.1.3.1: eContentType: " 290 "unknown OID: %s, want %s", fn, buf, obuf); 291 goto out; 292 } 293 294 /* Compare content-type with eContentType */ 295 octype = CMS_signed_get0_data_by_OBJ(si, cnt_type_oid, 296 -3, V_ASN1_OBJECT); 297 assert(octype != NULL); 298 if (OBJ_cmp(obj, octype) != 0) { 299 OBJ_obj2txt(buf, sizeof(buf), obj, 1); 300 OBJ_obj2txt(obuf, sizeof(obuf), octype, 1); 301 warnx("%s: RFC 6488: eContentType does not match Content-Type " 302 "OID: %s, want %s", fn, buf, obuf); 303 goto out; 304 } 305 306 /* 307 * Check that there are no CRLS in this CMS message. 308 */ 309 crls = CMS_get1_crls(cms); 310 if (crls != NULL) { 311 sk_X509_CRL_pop_free(crls, X509_CRL_free); 312 warnx("%s: RFC 6488: CMS has CRLs", fn); 313 goto out; 314 } 315 316 /* 317 * The self-signing certificate is further signed by the input 318 * signing authority according to RFC 6488, 2.1.4. 319 * We extract that certificate now for later verification. 320 */ 321 322 certs = CMS_get0_signers(cms); 323 if (certs == NULL || sk_X509_num(certs) != 1) { 324 warnx("%s: RFC 6488 section 2.1.4: eContent: " 325 "want 1 signer, have %d", fn, sk_X509_num(certs)); 326 goto out; 327 } 328 *xp = sk_X509_value(certs, 0); 329 if (!X509_up_ref(*xp)) { 330 *xp = NULL; 331 goto out; 332 } 333 334 /* Cache X509v3 extensions, see X509_check_ca(3). */ 335 if (X509_check_purpose(*xp, -1, -1) <= 0) { 336 warnx("%s: could not cache X509v3 extensions", fn); 337 goto out; 338 } 339 340 if (!x509_get_notafter(*xp, fn, ¬after)) 341 goto out; 342 if (*signtime > notafter) 343 warnx("%s: dating issue: CMS signing-time after X.509 notAfter", 344 fn); 345 346 if (CMS_SignerInfo_get0_signer_id(si, &kid, NULL, NULL) != 1 || 347 kid == NULL) { 348 warnx("%s: RFC 6488: could not extract SKI from SID", fn); 349 goto out; 350 } 351 if (CMS_SignerInfo_cert_cmp(si, *xp) != 0) { 352 warnx("%s: RFC 6488: wrong cert referenced by SignerInfo", fn); 353 goto out; 354 } 355 356 if (!cms_extract_econtent(fn, cms, res, rsz)) 357 goto out; 358 359 rc = 1; 360 out: 361 if (rc == 0) { 362 X509_free(*xp); 363 *xp = NULL; 364 } 365 sk_X509_free(certs); 366 CMS_ContentInfo_free(cms); 367 return rc; 368 } 369 370 /* 371 * Parse and validate a self-signed CMS message. 372 * Conforms to RFC 6488. 373 * The eContentType of the message must be an oid object. 374 * Return the eContent as a string and set "rsz" to be its length. 375 */ 376 unsigned char * 377 cms_parse_validate(X509 **xp, const char *fn, const unsigned char *der, 378 size_t derlen, const ASN1_OBJECT *oid, size_t *rsz, time_t *st) 379 { 380 unsigned char *res = NULL; 381 382 if (!cms_parse_validate_internal(xp, fn, der, derlen, oid, NULL, &res, 383 rsz, st)) 384 return NULL; 385 386 return res; 387 } 388 389 /* 390 * Parse and validate a detached CMS signature. 391 * bio must contain the original message, der must contain the CMS. 392 * Return the 1 on success, 0 on failure. 393 */ 394 int 395 cms_parse_validate_detached(X509 **xp, const char *fn, const unsigned char *der, 396 size_t derlen, const ASN1_OBJECT *oid, BIO *bio, time_t *st) 397 { 398 return cms_parse_validate_internal(xp, fn, der, derlen, oid, bio, NULL, 399 NULL, st); 400 } 401