| // Copyright 1995-2016 The OpenSSL Project Authors. All Rights Reserved. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // https://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #include <openssl/obj.h> | |
| #include <inttypes.h> | |
| #include <limits.h> | |
| #include <string.h> | |
| #include <iterator> | |
| #include <openssl/asn1.h> | |
| #include <openssl/bytestring.h> | |
| #include <openssl/err.h> | |
| #include <openssl/mem.h> | |
| #include "../asn1/internal.h" | |
| #include "../internal.h" | |
| #include "../lhash/internal.h" | |
| // obj_data.h must be included after the definition of `ASN1_OBJECT`. | |
| #include "obj_dat.h" | |
| using namespace bssl; | |
| BSSL_NAMESPACE_BEGIN | |
| DEFINE_LHASH_OF(ASN1_OBJECT) | |
| BSSL_NAMESPACE_END | |
| static StaticMutex global_added_lock; | |
| // These globals are protected by `global_added_lock`. | |
| static LHASH_OF(ASN1_OBJECT) *global_added_by_data = nullptr; | |
| static LHASH_OF(ASN1_OBJECT) *global_added_by_nid = nullptr; | |
| static LHASH_OF(ASN1_OBJECT) *global_added_by_short_name = nullptr; | |
| static LHASH_OF(ASN1_OBJECT) *global_added_by_long_name = nullptr; | |
| static StaticMutex global_next_nid_lock; | |
| static unsigned global_next_nid = NUM_NID; | |
| static int obj_next_nid() { | |
| MutexWriteLock lock(&global_next_nid_lock); | |
| return global_next_nid++; | |
| } | |
| ASN1_OBJECT *OBJ_dup(const ASN1_OBJECT *o) { | |
| if (o == nullptr) { | |
| return nullptr; | |
| } | |
| if (!(o->flags & ASN1_OBJECT_FLAG_DYNAMIC)) { | |
| // TODO(fork): this is a little dangerous. | |
| return const_cast<ASN1_OBJECT *>(o); | |
| } | |
| UniquePtr<ASN1_OBJECT> r(ASN1_OBJECT_new()); | |
| if (r == nullptr) { | |
| OPENSSL_PUT_ERROR(OBJ, ERR_R_ASN1_LIB); | |
| return nullptr; | |
| } | |
| // All fields of the object will be allocated. | |
| r->flags = o->flags | ASN1_OBJECT_FLAG_DYNAMIC | | |
| ASN1_OBJECT_FLAG_DYNAMIC_STRINGS | ASN1_OBJECT_FLAG_DYNAMIC_DATA; | |
| r->data = reinterpret_cast<uint8_t *>(OPENSSL_memdup(o->data, o->length)); | |
| if (o->length != 0 && r->data == nullptr) { | |
| return nullptr; | |
| } | |
| r->length = o->length; | |
| r->nid = o->nid; | |
| if (o->ln != nullptr) { | |
| r->ln = OPENSSL_strdup(o->ln); | |
| if (r->ln == nullptr) { | |
| return nullptr; | |
| } | |
| } | |
| if (o->sn != nullptr) { | |
| r->sn = OPENSSL_strdup(o->sn); | |
| if (r->sn == nullptr) { | |
| return nullptr; | |
| } | |
| } | |
| return r.release(); | |
| } | |
| int OBJ_cmp(const ASN1_OBJECT *a, const ASN1_OBJECT *b) { | |
| if (a->length < b->length) { | |
| return -1; | |
| } else if (a->length > b->length) { | |
| return 1; | |
| } | |
| return OPENSSL_memcmp(a->data, b->data, a->length); | |
| } | |
| const uint8_t *OBJ_get0_data(const ASN1_OBJECT *obj) { | |
| if (obj == nullptr) { | |
| return nullptr; | |
| } | |
| return obj->data; | |
| } | |
| size_t OBJ_length(const ASN1_OBJECT *obj) { | |
| if (obj == nullptr || obj->length < 0) { | |
| return 0; | |
| } | |
| return (size_t)obj->length; | |
| } | |
| static const ASN1_OBJECT *get_builtin_object(int nid) { | |
| // `NID_undef` is stored separately, so all the indices are off by one. The | |
| // caller of this function must have a valid built-in, non-undef NID. | |
| BSSL_CHECK(nid > 0 && nid < NUM_NID); | |
| return &kObjects[nid - 1]; | |
| } | |
| // obj_cmp is called to search the kNIDsInOIDOrder array. The `key` argument is | |
| // an `ASN1_OBJECT`* that we're looking for and `element` is a pointer to an | |
| // unsigned int in the array. | |
| static int obj_cmp(const void *key, const void *element) { | |
| uint16_t nid = *((const uint16_t *)element); | |
| return OBJ_cmp(reinterpret_cast<const ASN1_OBJECT *>(key), | |
| get_builtin_object(nid)); | |
| } | |
| int OBJ_obj2nid(const ASN1_OBJECT *obj) { | |
| if (obj == nullptr) { | |
| return NID_undef; | |
| } | |
| if (obj->nid != 0) { | |
| return obj->nid; | |
| } | |
| { | |
| MutexReadLock lock(&global_added_lock); | |
| if (global_added_by_data != nullptr) { | |
| ASN1_OBJECT *match = lh_ASN1_OBJECT_retrieve(global_added_by_data, obj); | |
| if (match != nullptr) { | |
| return match->nid; | |
| } | |
| } | |
| } | |
| const uint16_t *nid_ptr = reinterpret_cast<const uint16_t *>( | |
| bsearch(obj, kNIDsInOIDOrder, std::size(kNIDsInOIDOrder), | |
| sizeof(kNIDsInOIDOrder[0]), obj_cmp)); | |
| if (nid_ptr == nullptr) { | |
| return NID_undef; | |
| } | |
| return get_builtin_object(*nid_ptr)->nid; | |
| } | |
| int OBJ_cbs2nid(const CBS *cbs) { | |
| if (CBS_len(cbs) > INT_MAX) { | |
| return NID_undef; | |
| } | |
| ASN1_OBJECT obj; | |
| OPENSSL_memset(&obj, 0, sizeof(obj)); | |
| obj.data = CBS_data(cbs); | |
| obj.length = (int)CBS_len(cbs); | |
| return OBJ_obj2nid(&obj); | |
| } | |
| // short_name_cmp is called to search the kNIDsInShortNameOrder array. The | |
| // `key` argument is name that we're looking for and `element` is a pointer to | |
| // an unsigned int in the array. | |
| static int short_name_cmp(const void *key, const void *element) { | |
| const char *name = (const char *)key; | |
| uint16_t nid = *((const uint16_t *)element); | |
| return strcmp(name, get_builtin_object(nid)->sn); | |
| } | |
| int OBJ_sn2nid(const char *short_name) { | |
| { | |
| MutexReadLock lock(&global_added_lock); | |
| if (global_added_by_short_name != nullptr) { | |
| ASN1_OBJECT templ; | |
| templ.sn = short_name; | |
| ASN1_OBJECT *match = | |
| lh_ASN1_OBJECT_retrieve(global_added_by_short_name, &templ); | |
| if (match != nullptr) { | |
| return match->nid; | |
| } | |
| } | |
| } | |
| const uint16_t *nid_ptr = reinterpret_cast<const uint16_t *>(bsearch( | |
| short_name, kNIDsInShortNameOrder, std::size(kNIDsInShortNameOrder), | |
| sizeof(kNIDsInShortNameOrder[0]), short_name_cmp)); | |
| if (nid_ptr == nullptr) { | |
| return NID_undef; | |
| } | |
| return get_builtin_object(*nid_ptr)->nid; | |
| } | |
| // long_name_cmp is called to search the kNIDsInLongNameOrder array. The | |
| // `key` argument is name that we're looking for and `element` is a pointer to | |
| // an unsigned int in the array. | |
| static int long_name_cmp(const void *key, const void *element) { | |
| const char *name = (const char *)key; | |
| uint16_t nid = *((const uint16_t *)element); | |
| return strcmp(name, get_builtin_object(nid)->ln); | |
| } | |
| int OBJ_ln2nid(const char *long_name) { | |
| { | |
| MutexReadLock lock(&global_added_lock); | |
| if (global_added_by_long_name != nullptr) { | |
| ASN1_OBJECT templ; | |
| templ.ln = long_name; | |
| ASN1_OBJECT *match = | |
| lh_ASN1_OBJECT_retrieve(global_added_by_long_name, &templ); | |
| if (match != nullptr) { | |
| return match->nid; | |
| } | |
| } | |
| } | |
| const uint16_t *nid_ptr = reinterpret_cast<const uint16_t *>( | |
| bsearch(long_name, kNIDsInLongNameOrder, std::size(kNIDsInLongNameOrder), | |
| sizeof(kNIDsInLongNameOrder[0]), long_name_cmp)); | |
| if (nid_ptr == nullptr) { | |
| return NID_undef; | |
| } | |
| return get_builtin_object(*nid_ptr)->nid; | |
| } | |
| int OBJ_txt2nid(const char *s) { | |
| ASN1_OBJECT *obj; | |
| int nid; | |
| obj = OBJ_txt2obj(s, 0 /* search names */); | |
| nid = OBJ_obj2nid(obj); | |
| ASN1_OBJECT_free(obj); | |
| return nid; | |
| } | |
| OPENSSL_EXPORT int OBJ_nid2cbb(CBB *out, int nid) { | |
| const ASN1_OBJECT *obj = OBJ_nid2obj(nid); | |
| return obj != nullptr && | |
| CBB_add_asn1_element(out, CBS_ASN1_OBJECT, obj->data, obj->length); | |
| } | |
| const ASN1_OBJECT *OBJ_get_undef() { | |
| static const ASN1_OBJECT kUndef = { | |
| /*sn=*/SN_undef, | |
| /*ln=*/LN_undef, | |
| /*nid=*/NID_undef, | |
| /*length=*/0, | |
| /*data=*/nullptr, | |
| /*flags=*/0, | |
| }; | |
| return &kUndef; | |
| } | |
| ASN1_OBJECT *OBJ_nid2obj(int nid) { | |
| if (nid == NID_undef) { | |
| return (ASN1_OBJECT *)OBJ_get_undef(); | |
| } | |
| if (nid > 0 && nid < NUM_NID) { | |
| const ASN1_OBJECT *obj = get_builtin_object(nid); | |
| if (nid != NID_undef && obj->nid == NID_undef) { | |
| OPENSSL_PUT_ERROR(OBJ, OBJ_R_UNKNOWN_NID); | |
| return nullptr; | |
| } | |
| return (ASN1_OBJECT *)obj; | |
| } | |
| { | |
| MutexReadLock lock(&global_added_lock); | |
| if (global_added_by_nid != nullptr) { | |
| ASN1_OBJECT templ; | |
| templ.nid = nid; | |
| ASN1_OBJECT *match = lh_ASN1_OBJECT_retrieve(global_added_by_nid, &templ); | |
| if (match != nullptr) { | |
| return match; | |
| } | |
| } | |
| } | |
| OPENSSL_PUT_ERROR(OBJ, OBJ_R_UNKNOWN_NID); | |
| return nullptr; | |
| } | |
| const char *OBJ_nid2sn(int nid) { | |
| const ASN1_OBJECT *obj = OBJ_nid2obj(nid); | |
| if (obj == nullptr) { | |
| return nullptr; | |
| } | |
| return obj->sn; | |
| } | |
| const char *OBJ_nid2ln(int nid) { | |
| const ASN1_OBJECT *obj = OBJ_nid2obj(nid); | |
| if (obj == nullptr) { | |
| return nullptr; | |
| } | |
| return obj->ln; | |
| } | |
| static ASN1_OBJECT *create_object_with_text_oid(int (*get_nid)(), | |
| const char *oid, | |
| const char *short_name, | |
| const char *long_name) { | |
| uint8_t *buf; | |
| size_t len; | |
| CBB cbb; | |
| if (!CBB_init(&cbb, 32) || | |
| !CBB_add_asn1_oid_from_text(&cbb, oid, strlen(oid)) || | |
| !CBB_finish(&cbb, &buf, &len)) { | |
| OPENSSL_PUT_ERROR(OBJ, OBJ_R_INVALID_OID_STRING); | |
| CBB_cleanup(&cbb); | |
| return nullptr; | |
| } | |
| ASN1_OBJECT *ret = ASN1_OBJECT_create(get_nid ? get_nid() : NID_undef, buf, | |
| len, short_name, long_name); | |
| OPENSSL_free(buf); | |
| return ret; | |
| } | |
| ASN1_OBJECT *OBJ_txt2obj(const char *s, int dont_search_names) { | |
| if (!dont_search_names) { | |
| int nid = OBJ_sn2nid(s); | |
| if (nid == NID_undef) { | |
| nid = OBJ_ln2nid(s); | |
| } | |
| if (nid != NID_undef) { | |
| return OBJ_nid2obj(nid); | |
| } | |
| } | |
| return create_object_with_text_oid(nullptr, s, nullptr, nullptr); | |
| } | |
| static int strlcpy_int(char *dst, const char *src, int dst_size) { | |
| size_t ret = OPENSSL_strlcpy(dst, src, dst_size < 0 ? 0 : (size_t)dst_size); | |
| if (ret > INT_MAX) { | |
| OPENSSL_PUT_ERROR(OBJ, ERR_R_OVERFLOW); | |
| return -1; | |
| } | |
| return (int)ret; | |
| } | |
| int OBJ_obj2txt(char *out, int out_len, const ASN1_OBJECT *obj, | |
| int always_return_oid) { | |
| // Python depends on the empty OID successfully encoding as the empty | |
| // string. | |
| if (obj == nullptr || obj->length == 0) { | |
| return strlcpy_int(out, "", out_len); | |
| } | |
| if (!always_return_oid) { | |
| int nid = OBJ_obj2nid(obj); | |
| if (nid != NID_undef) { | |
| const char *name = OBJ_nid2ln(nid); | |
| if (name == nullptr) { | |
| name = OBJ_nid2sn(nid); | |
| } | |
| if (name != nullptr) { | |
| return strlcpy_int(out, name, out_len); | |
| } | |
| } | |
| } | |
| CBS cbs; | |
| CBS_init(&cbs, obj->data, obj->length); | |
| char *txt = CBS_asn1_oid_to_text(&cbs); | |
| if (txt == nullptr) { | |
| if (out_len > 0) { | |
| out[0] = '\0'; | |
| } | |
| return -1; | |
| } | |
| int ret = strlcpy_int(out, txt, out_len); | |
| OPENSSL_free(txt); | |
| return ret; | |
| } | |
| static uint32_t hash_nid(const ASN1_OBJECT *obj) { return obj->nid; } | |
| static int cmp_nid(const ASN1_OBJECT *a, const ASN1_OBJECT *b) { | |
| return a->nid - b->nid; | |
| } | |
| static uint32_t hash_data(const ASN1_OBJECT *obj) { | |
| return OPENSSL_hash32(obj->data, obj->length); | |
| } | |
| static uint32_t hash_short_name(const ASN1_OBJECT *obj) { | |
| return OPENSSL_strhash(obj->sn); | |
| } | |
| static int cmp_short_name(const ASN1_OBJECT *a, const ASN1_OBJECT *b) { | |
| return strcmp(a->sn, b->sn); | |
| } | |
| static uint32_t hash_long_name(const ASN1_OBJECT *obj) { | |
| return OPENSSL_strhash(obj->ln); | |
| } | |
| static int cmp_long_name(const ASN1_OBJECT *a, const ASN1_OBJECT *b) { | |
| return strcmp(a->ln, b->ln); | |
| } | |
| // obj_add_object inserts `obj` into the various global hashes for run-time | |
| // added objects. It returns one on success or zero otherwise. | |
| static int obj_add_object(ASN1_OBJECT *obj) { | |
| obj->flags &= ~(ASN1_OBJECT_FLAG_DYNAMIC | ASN1_OBJECT_FLAG_DYNAMIC_STRINGS | | |
| ASN1_OBJECT_FLAG_DYNAMIC_DATA); | |
| MutexWriteLock lock(&global_added_lock); | |
| if (global_added_by_nid == nullptr) { | |
| global_added_by_nid = lh_ASN1_OBJECT_new(hash_nid, cmp_nid); | |
| } | |
| if (global_added_by_data == nullptr) { | |
| global_added_by_data = lh_ASN1_OBJECT_new(hash_data, OBJ_cmp); | |
| } | |
| if (global_added_by_short_name == nullptr) { | |
| global_added_by_short_name = | |
| lh_ASN1_OBJECT_new(hash_short_name, cmp_short_name); | |
| } | |
| if (global_added_by_long_name == nullptr) { | |
| global_added_by_long_name = | |
| lh_ASN1_OBJECT_new(hash_long_name, cmp_long_name); | |
| } | |
| if (global_added_by_nid == nullptr || // | |
| global_added_by_data == nullptr || // | |
| global_added_by_short_name == nullptr || // | |
| global_added_by_long_name == nullptr) { | |
| return 0; | |
| } | |
| // We don't pay attention to `old_object` (which contains any previous object | |
| // that was evicted from the hashes) because we don't have a reference count | |
| // on ASN1_OBJECT values. Also, we should never have duplicates nids and so | |
| // should always have objects in `global_added_by_nid`. | |
| ASN1_OBJECT *old_object; | |
| int ok = lh_ASN1_OBJECT_insert(global_added_by_nid, &old_object, obj); | |
| if (obj->length != 0 && obj->data != nullptr) { | |
| ok &= lh_ASN1_OBJECT_insert(global_added_by_data, &old_object, obj); | |
| } | |
| if (obj->sn != nullptr) { | |
| ok &= lh_ASN1_OBJECT_insert(global_added_by_short_name, &old_object, obj); | |
| } | |
| if (obj->ln != nullptr) { | |
| ok &= lh_ASN1_OBJECT_insert(global_added_by_long_name, &old_object, obj); | |
| } | |
| return ok; | |
| } | |
| int OBJ_create(const char *oid, const char *short_name, const char *long_name) { | |
| ASN1_OBJECT *op = | |
| create_object_with_text_oid(obj_next_nid, oid, short_name, long_name); | |
| if (op == nullptr || !obj_add_object(op)) { | |
| return NID_undef; | |
| } | |
| return op->nid; | |
| } | |
| void OBJ_cleanup() {} |