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