This is the (as you noted, unofficial) escape hatch we added for alternate ASN.1 types that aren't UTF8String. We may revisit this once the ASN.1 DER work (https://github.com/pyca/cryptography/issues/12283) lands because then we'll have a public API for generating arbitrary ASN.1 structures, which then forces us to think a bit harder about the public API commitment for this.
Separately, the reason extensions appear in attributes is because that's actually how extensions are encoded in CSRs! Extensions are a dedicated field in tbsCertificate, but a CSR is this: CertificationRequestInfo ::= SEQUENCE { version INTEGER { v1(0) } (v1,...), subject Name, subjectPKInfo SubjectPublicKeyInfo{{ PKInfoAlgorithms }}, attributes [0] Attributes{{ CRIAttributes }} } We have the extensions convenience method on CSR, but that's just to make it easier to access the attribute that 99% of people want. -Paul (reaperhulk) On Wed, Apr 16, 2025 at 2:07 PM Ian Pilcher via Cryptography-dev < cryptography-dev@python.org> wrote: > I am working on a Let's Encrypt client that needs to create a new CSR, > signed with a new key, every time that it renews a certificate. AFAICT, > there really isn't any sort of "CSR template" format that I could put in > a configuration file, so my preferred approach would be to simply use an > existing CSR to store the name, attributes, and extensions, and then > "re-sign" it with the new key. > > Of course, it isn't actually possible to re-sign an existing CSR, so I > need to create a CSRBuilder, copy the name, attributes, and extensions > from the old CSR to the CSRBuilder, and create a new CSR that is signed > with my new key. > > The subject name and extensions are straightforward. > > csrb = x509.CertificateSigningRequestBuilder() > csrb = csrb.subject_name(csr.subject) > for ext in csr.extensions: > csrb = csrb.add_extension(ext.value, ext.critical) > > Attributes are a bit more complicated for a couple of reasons. > > First, the extension request (OID 1.2.840.113549.1.9.14) also shows up > as an attribute. It's simple to skip this particular OID when copying > the attributes. > > The second issue is that CSRBuilder.add_attribute() defaults to not > setting the "tag" of attributes that it adds, which causes > CSRBuilder.sign() to check that the attribute values are valid UTF-8 > (and raise an exception for any attribute whose value isn't UTF-8). > I am working around this by using the (undocumented) _tag parameter to > explicitly set it. > > CRMF_EXT_REQ_OID = x509.ObjectIdentifier('1.2.840.113549.1.9.14') > for attr in csr.attributes: > if attr.oid == CRMF_EXT_REQ_OID: > continue > try: > atype = x509.name._ASN1Type(attr._type) > except ValueError: > print(f'Unsupported attribute type: ' > f'oid={attr.oid}, type={attr._type}') > continue > csrb = csrb.add_attribute(attr.oid, attr.value, _tag=atype) > > Does this seem reasonable? Am I missing anything? > > Thanks! > > -- > ======================================================================== > If your user interface is intuitive in retrospect ... it isn't intuitive > ======================================================================== > > _______________________________________________ > Cryptography-dev mailing list > Cryptography-dev@python.org > https://mail.python.org/mailman/listinfo/cryptography-dev >
_______________________________________________ Cryptography-dev mailing list Cryptography-dev@python.org https://mail.python.org/mailman/listinfo/cryptography-dev