-
-
Notifications
You must be signed in to change notification settings - Fork 10.9k
Description
This is something @bob-beck and I have been talking about recently. We should make ASN1_STRING opaque.
Motivation
X509 object is currently quite inefficient. It needs to allocate small copies of every field in the certificate, which adds up, in both malloc overhead and redundancy with the cached TBSCertificate encoding. Given that X509 has to cache the TBSCertificate encoding anyway, a good implementation would simply alias the data into the underlying buffer.
Unfortunately, OpenSSL's X.509 API does not lend itself to a good implementation here. Imagine if ASN1_STRING had a mode where it did not own str->data and just kept a pointer to some buffer that was maintained elsewhere. We could have all those fields point into the cached encoding.
However, this would require two things:
- Every time someone tried to modify the
ASN1_STRING, we make a copy of the buffer first - There is a lifetime contract here. Before the underlying cache is dropped, we must go through and make a copy of all the strings.
The things that in X509 that make this difficult are:
- Although opaque, much of
X509is notconst-correct. ASN1_STRING_datagives mutable access to the underlying buffer, so OpenSSL must allow for callers to scribble over individual fields. That means it can't alias without causing other problems.ASN1_STRINGis exposed, so callers can get at the buffers even ifASN1_STRING_datawere deprecated
ASN1_STRING changes
Const-correctness is also helpful, as it generally helps you get a handle on your mutations (there's a lot of cached state on OpenSSL's X509 design, which is buggy at best), but I think the most important change here is making ASN1_STRING opaque and removing ASN1_STRING_data in favor of ASN1_STRING_get0_data.
ASN1_STRING_data is already deprecated, so hopefully you can just remove that. Or perhaps make it into an alias for the const-correct ASN1_STRING_get0_data.
To make ASN1_STRING opaque, the missing pieces are:
- No way to set
type - No way to read
flags - No way to set
flags
However, I do not think it makes sense to simply expose getters and setters for those. Making flags public allows callers to set ASN1_STRING_FLAG_EMBED at will and break the library. Setting ASN1_STRING_FLAG_NDEF and ASN1_STRING_FLAG_CONT is also questionable. Indeed, if we go through all your flags, I think ASN1_STRING_FLAG_BITS_LEFT is the only one with a public API use case, and that one is extremely difficult to use and not documented anywhere.
Likewise, I don't think it makes sense to set type independent of the other values. If you set type but leave data alone, now you have something in a completely unrelated format. If you set type but leave flags alone, now a non-BIT-STRING ASN1_STRING has unused bits!
New APIs
If we suppose the only useful public API feature of flags is the count of unused bits in a BIT STRING, that suggests a different set of APIs:
First, given an ASN1_STRING, return the number of unused bits. Now, this is a little subtle because OpenSSL has a mode where, if ASN1_STRING_FLAG_BITS_LEFT is clear, the true byte and bit length of the ASN1_STRING is truncated. So we may want an API that returns a tuple of (num_bytes, unused_bits).
Next, to construct ASN1_STRINGs, perhaps, a pair of APIs:
// ASN1_STRING_set1_bit_string sets |str| to contain a BIT STRING (i.e.
// type |V_ASN1_BIT_STRING|) containing |len| bytes from |in|. The last
// |unused_bits| of |in| will be unused. It returns one on success and
// zero on error.
//
// The following are error conditions and will cause the function to fail:
// - |unused_bits| is not between zero and seven.
// - |unused_bits| is non-zero and |len| is zero.
// - The bottom |unused_bits| of |in| are non-zero.
// - |len| is too large to fit in an |ASN1_STRING|.
int ASN1_STRING_set1_bit_string(
ASN1_BIT_STRING *str, const uint8_t *in, size_t len, int unused_bits);
// ASN1_STRING_set1_value sets |str| to contain a string of type |type|
// and value |len| bytes from |in|. It returns one on success and
// zero on error. If |len| is too large to fit in an |ASN1_STRING|, this
// function will fail and return an error.
//
// This function can only be used to set a whole number of bytes.
// For general BIT STRING support, use |ASN1_STRING_set1_bit_string|.
int ASN1_STRING_set1_value(
ASN1_STRING *str, int type, const uint8_t *in, size_t len);Or something in that vein.
Other flags
There are many more flags than ASN1_STRING_FLAG_BITS_LEFT. Going through them here:
ASN1_STRING_FLAG_NDEF and ASN1_STRING_FLAG_CONT seem to be internal book-keeping for the CMS implementation. I cannot tell what they are doing, but I suspect:
- This was the wrong design
- If a caller were to mess with them, the library would break
If so, that supports the assumption that they should not be exposed out of the public API.
ASN1_STRING_FLAG_MSTRING is set on MSTRING strings, though not consistently. (You can lose the flag if you X509_set1_notBefore, for example.) It is only queried in X509_time_adj_ex, which toggles whether this function preserves the UTCTime / GeneralizedTime disposition, or implements the X.509 Time choice and switches which it is.
Given that it is possible to lose the flag, I think the above applies: this is not the right design, and even if it were, it should not be possible to toggle it with public API. Rather, "adjust UTCTime", "adjust GeneralizedTime", and "adjust X.509 Time" are distinct operations and should have been distinct functions.
ASN1_STRING_FLAG_EMBED is extremely dangerous for a caller to set. It should not be public API.
ASN1_STRING_FLAG_X509_TIME was added in #3566 and is a pretty bizarre design. It is only used to control ossl_asn1_time_to_tm behavior, and only set on a temporary stack-allocated ASN1_STRING! This should not have leaked a weird flag into the public API and should instead have just passed a boolean in as an extra parameter to the function.
All this suggests that "flags" should remain an internal aspect of ASN1_STRING and not be exposed publicly.
Sub-issues
Metadata
Metadata
Assignees
Labels
Type
Projects
Status