Repository navigation
Expose SAN/IAN otherName entries via the FFI GeneralName API - #5903
moritzschmitt wants to merge 4 commits into
Conversation
5af71cf to
2510055
Compare
arckoor
left a comment
There was a problem hiding this comment.
Rough review only, and only really of the parts I'm comfortable with, i.e. FFI and Python.
I'm not sure exposing X509GeneralName in the python binding directly is a good call, I've been wanting to add something like this for a while, see this branch. I'm not convinced what's done there is the way to go either (since highly inefficient if you do want everything), but perhaps parts are still of interest
| ============== =================== | ||
| FFI Version Supported Starting | ||
| ============== =================== | ||
| 20260901 3.14.0 |
There was a problem hiding this comment.
3.14 would be 20261103, usually it's the date of the actual release. May be better to leave out completely and let it be handled in a separate PR, but as long as no one else opens a PR also looking to do this at the same time should probaly be fine :p
There was a problem hiding this comment.
Fair point, I wasn't sure about this either, but 3.14 was too tempting a version to leave unclaimed. If you'd rather keep the bump out of this PR, I'll drop it. Otherwise it can be adjusted to the actual release date the way the 3.11 stamp was.
| if(Botan::any_null_pointers(oid)) { | ||
| return BOTAN_FFI_ERROR_NULL_POINTER; | ||
| } |
There was a problem hiding this comment.
This before the #ifdef please, so behaviour re nullptrs doesn't change based on how the lib is compiled
There was a problem hiding this comment.
I went back and forth on this one and ended up following the immediate neighbours: the GeneralName functions already in the file (botan_x509_general_name_get_type, the *_alternative_names / *_name_constraints enumerators and their _count variants) all do their argument checks inside the #if, and so does botan_x509_cert_serial_number, which has the same shape (out-pointer for a new object plus a handle). The file has both styles: not_before, get_fingerprint, get_public_key and the ext_* getters check before the #if, the newer X.509 additions check inside.
The other thing that held me back is that moving the oid check alone doesn't quite give config-independent behaviour: the handle is validated by BOTAN_FFI_VISIT, which sits inside the #if in every function of this file, so (&oid, nullptr) still returns NOT_IMPLEMENTED in a build without X.509 either way. Since no botan_x509_general_name_t can exist in such a build at all, NOT_IMPLEMENTED for any argument combination seemed a reasonable answer to me.
That said, it's a two-line change and I don't feel strongly about it. If you'd rather have the out-pointer checked first, I'll move it, and would suggest doing the same for the rest of the GeneralName block in a small follow-up so the API stays uniform. Happy to do either.
There was a problem hiding this comment.
Yeah I agree, it's kind of a mess. The ones that use the FFI_VISIT macro will always perform differently, but for FFI functions I've written in the past that require an explicit nullptr check, I've usually followed the (mostly self-imposed) "check before ifdef" rule. It's mostly a matter of taste, not a big deal either way :)
Thanks for the pointer, I had a look and tried the branch against the test certs. I think the two approaches want different things: If a strings-only convenience for the common case would be useful, I'm happy to add one on top of the object list, in a single pass. |
|
The thought about the handle potentially not being freed is a good point, I hadn't considered (yet anyway). What might also work is wrapping the |
|
Agreed, a filter helper on top of the object list would be a natural follow-up. The one thing to decide there is what filter(DIRECTORY_NAME) returns, since that type has no string form. |
Previously GeneralName::decode_from only recorded that a name constraint is an otherName and dropped its type-id and value, so such constraints could neither be inspected nor re-encoded. Add a NameVariant alternative holding the type-id OID and the raw BER encoding of the inner value (reusing AlternativeName::OtherNameValue), the factory GeneralName::other_name and the accessor GeneralName::other_name_value. decode_from now parses the OtherName structure the same way AlternativeName::decode_from does and rejects malformed input, and encode_into emits it again, so otherName constraints round trip byte identically. GH randombit#5885
Certificates with otherName entries in the subject and issuer alternative names and in the name constraints, covering the Microsoft UPN, every string type that AlternativeName decodes into a string, string types it does not, non-string values, and empty and very long values. The README next to the data lists the contents and the expected values. Generated with OpenSSL 3.6.1 by running src/scripts/dev_tools/gen_othername_testdata.sh from the repository root. GH randombit#5885
The subject and issuer alternative name enumerators of the FFI omitted otherName entries, and botan_x509_general_name_get_type refused otherName handles, which were reachable only via the name constraint accessors. The Microsoft UPN used for certificate based authentication against Active Directory is such an entry, so it was not accessible to FFI users at all. Enumerate otherNames after the other name types, let botan_x509_general_name_get_type report BOTAN_X509_OTHER_NAME, view the raw BER encoding of the value via botan_x509_general_name_view_binary_value and, for UTF8String and UTF-8 subset string values, the string via botan_x509_general_name_view_string_value. The new function botan_x509_general_name_other_name_type_id returns the type-id as an OID object. Bump the FFI API version and drop the outdated remarks that otherNames cannot be accessed via the FFI. GH randombit#5885
Bind the botan_x509_general_name_t functions, including the enumerators for subject/issuer alternative names and name constraints that the binding never covered, as X509GeneralName and X509GeneralNameType, and add the corresponding accessors to X509Cert. The module now targets the FFI API version introduced with the otherName support in 3.14.0. GH randombit#5885
2510055 to
3db43f1
Compare
Fixes #5885, implementing the direction agreed on the issue: otherName entries are surfaced through the existing
botan_x509_general_name_tenumeration (Option A), with the type-id returned as the FFI's first-class OID object rather than as a string.C++ library
Botan::GeneralNamenow retains the otherName payload (type-id OID plus the raw inner value) in itsNameVariant, reusingAlternativeName::OtherNameValue. A factoryGeneralName::other_name()and an accessorGeneralName::other_name_value()are added,GeneralName::decode_from()retains type-id and value for otherName name constraints (using the same structure checks asAlternativeName::decode_from()), andGeneralName::encode_into()can now encode otherNames, so the corpus re-encode test covers them.One consequence: a malformed otherName in a NameConstraints extension (value not wrapped in the
EXPLICIT [0]tag, missing type-id or value, trailing content) now fails to decode, where previously it was retained as an opaque entry without a payload. The extension then becomes anUnknown_Extensionwithfailed_to_decodeset, and path validation reportsEXTENSION_ENCODING_ERROR(matching how a malformed otherName in a SubjectAlternativeName already behaves).FFI
botan_x509_cert_subject_alternative_names(),botan_x509_cert_issuer_alternative_names()and the name constraint enumerators now yield otherName entries (appended after the previously supported types, so existing indices are unchanged), andbotan_x509_general_name_get_type()returnsBOTAN_X509_OTHER_NAMEinstead of erroring. For an otherName:botan_x509_general_name_other_name_type_id()returns the type-id as a new OID object (to be freed withbotan_oid_destroy), followingbotan_pubkey_oid(); callers can compare it viabotan_oid_equal()against e.g.botan_oid_from_string("Microsoft UPN").botan_x509_general_name_view_binary_value()views the raw BER of the value, i.e. the complete TLV inside theEXPLICIT [0]tag (exactly whatAlternativeName::OtherNameValue::value()holds).botan_x509_general_name_view_string_value()succeeds for values of the string types thatAlternativeName::decode_from()also decodes into the deprecatedother_names()set — UTF8String, IA5String, PrintableString, VisibleString and NumericString with content valid for the type — which directly covers the Microsoft UPN. Since the string view hands out a NUL-terminated C string, values with an embedded NUL are additionally rejected; both view functions document this. Anything else returnsBOTAN_FFI_ERROR_INVALID_OBJECT_STATE.The FFI changes are additive (
new API version 20260901API stamps are bumped at release time, exports tagged 3.14). As discussed in the issue, existing callers will observe additional SAN/IAN entries,get_type()succeeds for otherName name constraints where it previously errored, and SmtpUTF8Mailbox entries (RFC 9598) appear as otherNames. The stale "no way to access OTHER_NAME" comment inffi.hand the "deprecated" sentence inffi.rstare removed.Python binding
botan3.pypreviously had no bindings for the GeneralName API at all; this addsX509GeneralName/X509GeneralNameTypeplus theX509Certaccessors for subject/issuer alternative names and permitted/excluded name constraints, along with documentation and tests.Test data
src/tests/data/x509/othername/contains generated certificates covering the msUPN case, every accepted and rejected string type, non-string values, multiple otherNames beside other name types, an IssuerAlternativeName otherName, otherName name constraints, and an empty as well as a 3000-byte value. The generator script is committed (src/scripts/dev_tools/gen_othername_testdata.sh); the README documents each certificate and the expected type-ids and inner values, which were read offopenssl asn1parseoutput independently of the implementation. String values that are invalid for their tag cannot be produced with OpenSSL, so one certificate is built in the test itself via the C++ API. The existing otherName certificates undername_constraints/are reused to pin the behavior for SmtpUTF8Mailbox, SRVName, permanentIdentifier and SIM entries.