1 /* $OpenBSD: cms.c,v 1.16 2022/03/28 13:04:01 claudio 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 <stdarg.h> 21 #include <stdint.h> 22 #include <stdlib.h> 23 #include <string.h> 24 #include <unistd.h> 25 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 /* 36 * Parse and validate a self-signed CMS message, where the signing X509 37 * certificate has been hashed to dgst (optional). 38 * Conforms to RFC 6488. 39 * The eContentType of the message must be an oid object. 40 * Return the eContent as a string and set "rsz" to be its length. 41 */ 42 unsigned char * 43 cms_parse_validate(X509 **xp, const char *fn, const unsigned char *der, 44 size_t derlen, const ASN1_OBJECT *oid, size_t *rsz) 45 { 46 char buf[128], obuf[128]; 47 const ASN1_OBJECT *obj, *octype; 48 ASN1_OCTET_STRING **os = NULL, *kid = NULL; 49 CMS_ContentInfo *cms; 50 int rc = 0; 51 STACK_OF(X509) *certs = NULL; 52 STACK_OF(X509_CRL) *crls; 53 STACK_OF(CMS_SignerInfo) *sinfos; 54 CMS_SignerInfo *si; 55 X509_ALGOR *pdig, *psig; 56 unsigned char *res = NULL; 57 int i, nattrs, nid; 58 int has_ct = 0, has_md = 0, has_st = 0, 59 has_bst = 0; 60 61 *rsz = 0; 62 *xp = NULL; 63 64 /* just fail for empty buffers, the warning was printed elsewhere */ 65 if (der == NULL) 66 return NULL; 67 68 if ((cms = d2i_CMS_ContentInfo(NULL, &der, derlen)) == NULL) { 69 cryptowarnx("%s: RFC 6488: failed CMS parse", fn); 70 goto out; 71 } 72 73 /* 74 * The CMS is self-signed with a signing certifiate. 75 * Verify that the self-signage is correct. 76 */ 77 78 if (!CMS_verify(cms, NULL, NULL, NULL, NULL, 79 CMS_NO_SIGNER_CERT_VERIFY)) { 80 cryptowarnx("%s: RFC 6488: CMS not self-signed", fn); 81 goto out; 82 } 83 84 /* RFC 6488 section 3 verify the CMS */ 85 /* the version of SignedData and SignerInfos can't be verified */ 86 87 sinfos = CMS_get0_SignerInfos(cms); 88 assert(sinfos != NULL); 89 if (sk_CMS_SignerInfo_num(sinfos) != 1) { 90 cryptowarnx("%s: RFC 6488: CMS has multiple signerInfos", fn); 91 goto out; 92 } 93 si = sk_CMS_SignerInfo_value(sinfos, 0); 94 95 nattrs = CMS_signed_get_attr_count(si); 96 if (nattrs <= 0) { 97 cryptowarnx("%s: RFC 6488: error extracting signedAttrs", fn); 98 goto out; 99 } 100 for (i = 0; i < nattrs; i++) { 101 X509_ATTRIBUTE *attr; 102 103 attr = CMS_signed_get_attr(si, i); 104 if (attr == NULL || X509_ATTRIBUTE_count(attr) != 1) { 105 cryptowarnx("%s: RFC 6488: " 106 "bad signed attribute encoding", fn); 107 goto out; 108 } 109 110 obj = X509_ATTRIBUTE_get0_object(attr); 111 if (obj == NULL) { 112 cryptowarnx("%s: RFC 6488: bad signed attribute", fn); 113 goto out; 114 } 115 if (OBJ_cmp(obj, cnt_type_oid) == 0) { 116 if (has_ct++ != 0) { 117 cryptowarnx("%s: RFC 6488: duplicate " 118 "signed attribute", fn); 119 goto out; 120 } 121 } else if (OBJ_cmp(obj, msg_dgst_oid) == 0) { 122 if (has_md++ != 0) { 123 cryptowarnx("%s: RFC 6488: duplicate " 124 "signed attribute", fn); 125 goto out; 126 } 127 } else if (OBJ_cmp(obj, sign_time_oid) == 0) { 128 if (has_st++ != 0) { 129 cryptowarnx("%s: RFC 6488: duplicate " 130 "signed attribute", fn); 131 goto out; 132 } 133 } else if (OBJ_cmp(obj, bin_sign_time_oid) == 0) { 134 if (has_bst++ != 0) { 135 cryptowarnx("%s: RFC 6488: duplicate " 136 "signed attribute", fn); 137 goto out; 138 } 139 } else { 140 OBJ_obj2txt(buf, sizeof(buf), obj, 1); 141 cryptowarnx("%s: RFC 6488: " 142 "CMS has unexpected signed attribute %s", 143 fn, buf); 144 goto out; 145 } 146 } 147 if (!has_ct || !has_md) { 148 cryptowarnx("%s: RFC 6488: CMS missing required " 149 "signed attribute", fn); 150 goto out; 151 } 152 if (CMS_unsigned_get_attr_count(si) != -1) { 153 cryptowarnx("%s: RFC 6488: CMS has unsignedAttrs", fn); 154 goto out; 155 } 156 157 /* Check digest and signature algorithms */ 158 CMS_SignerInfo_get0_algs(si, NULL, NULL, &pdig, &psig); 159 X509_ALGOR_get0(&obj, NULL, NULL, pdig); 160 nid = OBJ_obj2nid(obj); 161 if (nid != NID_sha256) { 162 warnx("%s: RFC 6488: wrong digest %s, want %s", fn, 163 OBJ_nid2ln(nid), OBJ_nid2ln(NID_sha256)); 164 goto out; 165 } 166 X509_ALGOR_get0(&obj, NULL, NULL, psig); 167 nid = OBJ_obj2nid(obj); 168 /* RFC7395 last paragraph of section 2 specifies the allowed psig */ 169 if (nid != NID_rsaEncryption && nid != NID_sha256WithRSAEncryption) { 170 warnx("%s: RFC 6488: wrong signature algorithm %s, want %s", 171 fn, OBJ_nid2ln(nid), OBJ_nid2ln(NID_rsaEncryption)); 172 goto out; 173 } 174 175 /* RFC 6488 section 2.1.3.1: check the object's eContentType. */ 176 177 obj = CMS_get0_eContentType(cms); 178 if (obj == NULL) { 179 warnx("%s: RFC 6488 section 2.1.3.1: eContentType: " 180 "OID object is NULL", fn); 181 goto out; 182 } 183 if (OBJ_cmp(obj, oid) != 0) { 184 OBJ_obj2txt(buf, sizeof(buf), obj, 1); 185 OBJ_obj2txt(obuf, sizeof(obuf), oid, 1); 186 warnx("%s: RFC 6488 section 2.1.3.1: eContentType: " 187 "unknown OID: %s, want %s", fn, buf, obuf); 188 goto out; 189 } 190 191 /* Compare content-type with eContentType */ 192 octype = CMS_signed_get0_data_by_OBJ(si, cnt_type_oid, 193 -3, V_ASN1_OBJECT); 194 assert(octype != NULL); 195 if (OBJ_cmp(obj, octype) != 0) { 196 OBJ_obj2txt(buf, sizeof(buf), obj, 1); 197 OBJ_obj2txt(obuf, sizeof(obuf), oid, 1); 198 warnx("%s: RFC 6488: eContentType does not match Content-Type " 199 "OID: %s, want %s", fn, buf, obuf); 200 goto out; 201 } 202 203 /* 204 * Check that there are no CRLS in this CMS message. 205 */ 206 crls = CMS_get1_crls(cms); 207 if (crls != NULL) { 208 sk_X509_CRL_pop_free(crls, X509_CRL_free); 209 cryptowarnx("%s: RFC 6488: CMS has CRLs", fn); 210 goto out; 211 } 212 213 /* 214 * The self-signing certificate is further signed by the input 215 * signing authority according to RFC 6488, 2.1.4. 216 * We extract that certificate now for later verification. 217 */ 218 219 certs = CMS_get0_signers(cms); 220 if (certs == NULL || sk_X509_num(certs) != 1) { 221 warnx("%s: RFC 6488 section 2.1.4: eContent: " 222 "want 1 signer, have %d", fn, sk_X509_num(certs)); 223 goto out; 224 } 225 *xp = X509_dup(sk_X509_value(certs, 0)); 226 227 if (CMS_SignerInfo_get0_signer_id(si, &kid, NULL, NULL) != 1 || 228 kid == NULL) { 229 warnx("%s: RFC 6488: could not extract SKI from SID", fn); 230 goto out; 231 } 232 if (CMS_SignerInfo_cert_cmp(si, *xp) != 0) { 233 warnx("%s: RFC 6488: wrong cert referenced by SignerInfo", fn); 234 goto out; 235 } 236 237 /* Verify that we have eContent to disseminate. */ 238 239 if ((os = CMS_get0_content(cms)) == NULL || *os == NULL) { 240 warnx("%s: RFC 6488 section 2.1.4: " 241 "eContent: zero-length content", fn); 242 goto out; 243 } 244 245 /* 246 * Extract and duplicate the eContent. 247 * The CMS framework offers us no other way of easily managing 248 * this information; and since we're going to d2i it anyway, 249 * simply pass it as the desired underlying types. 250 */ 251 252 if ((res = malloc((*os)->length)) == NULL) 253 err(1, NULL); 254 memcpy(res, (*os)->data, (*os)->length); 255 *rsz = (*os)->length; 256 257 rc = 1; 258 out: 259 sk_X509_free(certs); 260 CMS_ContentInfo_free(cms); 261 262 if (rc == 0) { 263 X509_free(*xp); 264 *xp = NULL; 265 } 266 267 return res; 268 } 269 270 /* 271 * Wrapper around ASN1_get_object() that preserves the current start 272 * state and returns a more meaningful value. 273 * Return zero on failure, non-zero on success. 274 */ 275 int 276 ASN1_frame(const char *fn, size_t sz, 277 const unsigned char **cnt, long *cntsz, int *tag) 278 { 279 int ret, pcls; 280 281 ret = ASN1_get_object(cnt, cntsz, tag, &pcls, sz); 282 if ((ret & 0x80)) { 283 cryptowarnx("%s: ASN1_get_object", fn); 284 return 0; 285 } 286 return ASN1_object_size((ret & 0x01) ? 2 : 0, *cntsz, *tag); 287 } 288 289 /* 290 * Check the version field in eContent. 291 * Returns -1 on failure, zero on success. 292 */ 293 int 294 cms_econtent_version(const char *fn, const unsigned char **d, size_t dsz, 295 long *version) 296 { 297 ASN1_INTEGER *aint = NULL; 298 long plen; 299 int ptag, rc = -1; 300 301 if (!ASN1_frame(fn, dsz, d, &plen, &ptag)) 302 goto out; 303 if (ptag != 0) { 304 warnx("%s: eContent version: expected explicit tag [0]", fn); 305 goto out; 306 } 307 308 aint = d2i_ASN1_INTEGER(NULL, d, plen); 309 if (aint == NULL) { 310 cryptowarnx("%s: eContent version: failed d2i_ASN1_INTEGER", 311 fn); 312 goto out; 313 } 314 315 *version = ASN1_INTEGER_get(aint); 316 if (*version < 0) { 317 warnx("%s: eContent version: expected positive integer, got:" 318 " %ld", fn, *version); 319 goto out; 320 } 321 322 rc = 0; 323 out: 324 ASN1_INTEGER_free(aint); 325 return rc; 326 } 327