1 /* $OpenBSD: cms.c,v 1.10 2021/09/09 14:15:49 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 /* 31 * Parse and validate a self-signed CMS message, where the signing X509 32 * certificate has been hashed to dgst (optional). 33 * Conforms to RFC 6488. 34 * The eContentType of the message must be an oid object. 35 * Return the eContent as a string and set "rsz" to be its length. 36 */ 37 unsigned char * 38 cms_parse_validate(X509 **xp, const char *fn, const ASN1_OBJECT *oid, 39 size_t *rsz) 40 { 41 const ASN1_OBJECT *obj; 42 ASN1_OCTET_STRING **os = NULL; 43 BIO *bio = NULL; 44 CMS_ContentInfo *cms; 45 FILE *f; 46 int rc = 0; 47 STACK_OF(X509) *certs = NULL; 48 unsigned char *res = NULL; 49 50 *rsz = 0; 51 *xp = NULL; 52 53 /* 54 * This is usually fopen() failure, so let it pass through to 55 * the handler, which will in turn ignore the entity. 56 */ 57 if ((f = fopen(fn, "rb")) == NULL) { 58 warn("%s", fn); 59 return NULL; 60 } 61 62 if ((bio = BIO_new_fp(f, BIO_CLOSE)) == NULL) { 63 cryptowarnx("%s: BIO_new_fp", fn); 64 return NULL; 65 } 66 67 if ((cms = d2i_CMS_bio(bio, NULL)) == NULL) { 68 cryptowarnx("%s: RFC 6488: failed CMS parse", fn); 69 goto out; 70 } 71 72 /* 73 * The CMS is self-signed with a signing certifiate. 74 * Verify that the self-signage is correct. 75 */ 76 77 if (!CMS_verify(cms, NULL, NULL, 78 NULL, NULL, CMS_NO_SIGNER_CERT_VERIFY)) { 79 cryptowarnx("%s: RFC 6488: CMS not self-signed", fn); 80 goto out; 81 } 82 83 /* RFC 6488 section 2.1.3.1: check the object's eContentType. */ 84 85 obj = CMS_get0_eContentType(cms); 86 if (obj == NULL) { 87 warnx("%s: RFC 6488 section 2.1.3.1: eContentType: " 88 "OID object is NULL", fn); 89 goto out; 90 } 91 if (OBJ_cmp(obj, oid) != 0) { 92 char buf[128], obuf[128]; 93 94 OBJ_obj2txt(buf, sizeof(buf), obj, 1); 95 OBJ_obj2txt(obuf, sizeof(obuf), oid, 1); 96 warnx("%s: RFC 6488 section 2.1.3.1: eContentType: " 97 "unknown OID: %s, want %s", fn, buf, obuf); 98 goto out; 99 } 100 101 /* 102 * The self-signing certificate is further signed by the input 103 * signing authority according to RFC 6488, 2.1.4. 104 * We extract that certificate now for later verification. 105 */ 106 107 certs = CMS_get0_signers(cms); 108 if (certs == NULL || sk_X509_num(certs) != 1) { 109 warnx("%s: RFC 6488 section 2.1.4: eContent: " 110 "want 1 signer, have %d", fn, sk_X509_num(certs)); 111 goto out; 112 } 113 *xp = X509_dup(sk_X509_value(certs, 0)); 114 115 /* Verify that we have eContent to disseminate. */ 116 117 if ((os = CMS_get0_content(cms)) == NULL || *os == NULL) { 118 warnx("%s: RFC 6488 section 2.1.4: " 119 "eContent: zero-length content", fn); 120 goto out; 121 } 122 123 /* 124 * Extract and duplicate the eContent. 125 * The CMS framework offers us no other way of easily managing 126 * this information; and since we're going to d2i it anyway, 127 * simply pass it as the desired underlying types. 128 */ 129 130 if ((res = malloc((*os)->length)) == NULL) 131 err(1, NULL); 132 memcpy(res, (*os)->data, (*os)->length); 133 *rsz = (*os)->length; 134 135 rc = 1; 136 out: 137 BIO_free_all(bio); 138 sk_X509_free(certs); 139 CMS_ContentInfo_free(cms); 140 141 if (rc == 0) { 142 X509_free(*xp); 143 *xp = NULL; 144 } 145 146 return res; 147 } 148 149 /* 150 * Wrapper around ASN1_get_object() that preserves the current start 151 * state and returns a more meaningful value. 152 * Return zero on failure, non-zero on success. 153 */ 154 int 155 ASN1_frame(const char *fn, size_t sz, 156 const unsigned char **cnt, long *cntsz, int *tag) 157 { 158 int ret, pcls; 159 160 ret = ASN1_get_object(cnt, cntsz, tag, &pcls, sz); 161 if ((ret & 0x80)) { 162 cryptowarnx("%s: ASN1_get_object", fn); 163 return 0; 164 } 165 return ASN1_object_size((ret & 0x01) ? 2 : 0, *cntsz, *tag); 166 } 167 168 /* 169 * Check the version field in eContent. 170 * Returns -1 on failure, zero on success. 171 */ 172 int 173 cms_econtent_version(const char *fn, const unsigned char **d, size_t dsz, 174 long *version) 175 { 176 ASN1_INTEGER *aint = NULL; 177 long plen; 178 int ptag, rc = -1; 179 180 if (!ASN1_frame(fn, dsz, d, &plen, &ptag)) 181 goto out; 182 if (ptag != 0) { 183 warnx("%s: eContent version: expected explicit tag [0]", fn); 184 goto out; 185 } 186 187 aint = d2i_ASN1_INTEGER(NULL, d, plen); 188 if (aint == NULL) { 189 cryptowarnx("%s: eContent version: failed d2i_ASN1_INTEGER", 190 fn); 191 goto out; 192 } 193 194 *version = ASN1_INTEGER_get(aint); 195 if (*version < 0) { 196 warnx("%s: eContent version: expected positive integer, got:" 197 " %ld", fn, *version); 198 goto out; 199 } 200 201 rc = 0; 202 out: 203 ASN1_INTEGER_free(aint); 204 return rc; 205 } 206