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