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

Reply via email to