Twistlock logo png sticker cybersecurity

Trying to set up a crude prototype system to test BIMI using open-source tools

This was originally posted on the Information Security StackExchange (a technical Q&A site) but I was told that it was off-topic and instructed to delete it and post it on a blog. So, well, here it is. May it be useful to someone googling for an answer!

TL;DR? I’m looking for information on how to embed a logotype on a X.509 PEM certificate, for the purposes of doing some tests with BIMI, using OpenSSL to generate a CSR. I have assembled all bits and pieces I need, except figuring out how to ‘convert’ the ASN.1 definition of the logotype-embedding extension into a so-called ‘configuration file’ that the OpenSSL CLI will accept.


So, BIMI. You know, getting your organisation’s cute little SVG logo on Gmail and other mailbox providers. A nice concept — although I refrain from commenting on its real usefulness — but… a rather expensive one! Even if it’s just for testing purposes…

At the date of writing, to get BIMI logotypes to display on Gmail (& others), you will need to certify that the logotype is yours and your domain must be fully DMARC-compliant. Currently, only two organisations provide that service — DigiCert and Entrust — and, allegedly, because of the manual steps involved (figuring out if your certificate of ownership of the logotype, as well as the domain name you wish to assign it via BIMI, are all valid), they charge US$1000-1500/year just for that ‘simple’ service (!). Since there is no competition — say, from Let’s Encrypt or ZeroSSL or others — they are the only source of so-called Verified Mark Certificates (VMCs) and can charge whatever you want. The trouble is that you cannot easily do a trial — just to see how it works — because, well, those services are far too expensive for humble organisations (non-profits, small businesses, startups that aren’t unicorns, and so forth).

On the DNS side, things are relatively easy to set up, according to the latest current IETF RFC on the subject.

A VMC is, by itself, something rather simple and very standard — it’s just a ‘normal’ X.509 certificate, signed by a trusted CA (note: the RFC does not provide a list of ‘recommended’ trusted CAs; in this context, it means ‘CAs trusted by the mailbox provider’ or even just the MUA, at their discretion), which includes a logotype as per RFC6170 (or any subsequent revision), with the caveat that the logotype can only be a SVG logo with the Tiny SVG 1.2 profile (with a few additional limitations imposed by the BIMI group).

The VMC therefore includes an extension, using the facilities for adding modules to a X.509 certificate, and the relevant ASN.1 definitions are published by RFC6170 (and posterior RFCs).

Generating a valid logo is not too hard — even if some steps might require manual human processing (read: manually editing the SVG file and shuffling XML blocks/attributes around) — and there are plenty of tools to do the conformity validation.

Once the logo is generated, and properly embedded into a CSR, it can be submitted to a CA for signing it, which should reply with the PEM file to be referenced via the BIMI DNS TXT entry.

So far, so good.

The commercial registrars (e.g. DigiCert and Entrust) make this process very user-friendly: there is an online Web form where you place your logo and whatever documentation they require (proof of logotype ownership and so forth), do the payment of their fees, and wait a few days for manual processing (and possibly some phone calls and visits to a notary public…). That’s it. All the process of handling the CSR is done ‘under the hood’ and not really required.

As said, there are plenty of online validators out there (from the BIMI group, from MXToolbox, etc.). They all also work ‘under the hood’: you basically provide them with a domain name, and their validator will read the associated BIMI TXT record, grab the logo and the PEM file, check if the logo is valid, check if the PEM file is a valid certificate, and present you with the results, sometimes offering a few hints regarding non-conformity.

Dealing with those issues related to DNS and the SVG format was easy for me; what I’m currently struggling with is how to generate a CSR using the CLI for OpenSSL (3.0 or 3.1 preferred, but I’m ok with old versions, so long as they work) to include the logotype during the generation phase.

From what I could see, it’s relatively simple to automate the collection of the required standard parameters (as opposed to asking the user for interactively typing them). You can add a handful of additional parameters directly on the command-line, but it quickly grows out of proportion. Instead, OpenSSL uses what they call a ‘configuration file’ (see also: man page), and suggest that such a ‘configuration file’ is to be used to define so-called ‘arbitrary extensions’ — those that, by definition, are not standard.

As you can see from the examples on the last page, this defines an alternative syntax to ASN.1 (!!!) to set up additional parameters, fields, etc. Ultimately, these will be ‘transpiled’ (to borrow the term from MediaWiki…) into ASN.1 encoding at some point, but, alas, sort of delving deep into the OpenSSL code to see how they do it, it’s a bit obscure to me how exactly to ‘convert’ from ASN.1 to OpenSSL’s own syntax, except, of course, for very basic and trivial cases.

Start delving deep into these questions, and search engines will show all sort of links to comments on StackExchange or Reddit with users saying ‘OpenSSL was not meant to do that’, ‘you could do the ASN.1 parsing in C, calling the OpenSSL API as needed’, or, more likely, ‘you should forget about using OpenSSL and use a ASN.1-specific tool/compiler‘. These comments are not necessarily directly related to embedding logotypes but apply to other cases where the OP wanted to know how to use OpenSSL to encode extension X and stumbled upon similar issues.

Needless to say, most of those tools recommended by the ITU are heavy-weight behemoths from commercial enterprises (with a handful of free and open-source ones) that target the telecommunications market, and getting a license for them requires a second and third mortgage on your home 🙂 And you’d be swatting at a fly with a nuclear bomb.

OpenSSL, for all its faults and limitations, is relatively compact and does, well, perhaps 99% of what I need — and does it quite well. There is support for the remaining 1%. However, it seems to me (and remember, I’m a newbie in all this) that a straight conversion from the ASN.1 on a RFC to OpenSSL’s ‘configuration format’ requires both extensive knowledge of ASN.1 (which I don’t claim to have) and that file format (which I’m pretty sure I don’t know anything about, and just heard about it today for the first time). It’s also clear to me that one of the purposes of this config file format is to automate the collection of data from a user typing commands on a terminal to generate their keys, certs, or CSRs, and designed mostly for that purpose (nevertheless, since so-called ‘arbitrary extensions’ might also require human input, the OpenSSL team has implemented access to those).

My case is simple. Actually, I just need to add an external URL to my BIMI-compliant SVG logo. I could directly embed it inside the CSR itself — and get it signed — but that requires additional processing (gzipping the SVG, using base64 to encode it, etc.) and is not mandatory anyway; a good, old, plain, URL is perfectly valid according to the RFC and the RFC editors even suggest that it might be the ‘best’ scenario (when, for example, the organisation relies upon a third party to provide the logo design, as opposed to having it in-house and readily available). At some point in the process, whatever the origin of the SVG might be, it will be retrieved and attached and re-encoded and DKIM-signed and what not — all things I don’t need to really worry about at this stage, since that’s a nightmare for the MUA, not for me.

You can very easily retrieve the Verified Mark Certificate from any organisation which has embraced BIMI. For instance, at the time of writing, this was the PEM cert for CNN. This link will almost certainly suffer from rot (they will periodically update it, I’m sure), so here is the relevant part, extracted simply by digging for the correct record:

$ dig default._bimi.cnn.com txt
[...]
default._bimi.cnn.com.  1800    IN      TXT     "v=BIMI1; l=https://amplify.valimail.com/bimi/time-warner/rWgzqvey7wX-cable_news_network_inc.svg; a=https://amplify.valimail.com/bimi/time-warner/rWgzqvey7wX-cable_news_network_inc.pem"
Code language: Bash (bash)

and then doing openssl asn1parse -in rWgzqvey7wX-cable_news_network_inc.pem (OpenSSL version 3.1, retrieved via Homebrew on macOS):

 [...]
 1228:d=4  hl=2 l=  12 cons: SEQUENCE
 1230:d=5  hl=2 l=   3 prim: OBJECT            :X509v3 Basic Constraints
 1235:d=5  hl=2 l=   1 prim: BOOLEAN           :255
 1238:d=5  hl=2 l=   2 prim: OCTET STRING      [HEX DUMP]:3000
 1242:d=4  hl=4 l=1292 cons: SEQUENCE
 1246:d=5  hl=2 l=   8 prim: OBJECT            :1.3.6.1.5.5.7.1.12
 1256:d=5  hl=4 l=1278 prim: OCTET STRING      [HEX DUMP]:308204FAA28204F6A08204F2308204EE308204EA308204E6160D696D6167652F7376672B786D6C30233021300906052B0E03021A05000414EA8C81DA633C66A16262134A78576CDF067638E9308204AE168204AA646174613A696D6167652F7376672B786D6C3B6261736536342C4834734941414141414141414370315554572F624F42413978372B437135344B6B445348464C2B4D4F45556246453242626C46674156385872714C47776D70745131626B744C392B3331414A306B4E515941736B354869476E486E7A356C47586278372B3763585544716675734639587045306C326E317A754F3332642B76716676796D5576586D616E483568314C6951377476682B313447466269376533686179732B39763339615377755957754E79314C3874666B67336A386344384D6F76765433642B726A58756A69334D773156694A6F593853372B36362F4665613145456F682F576D362B786D457263545837616E394D68792B6458323772735A752F3130645435586F62746656702B3333647669624B67486B2B394F36326F336A636256636E73396E665862364D4E77747254466D695A53[...]

or, if you prefer, directly with openssl x509 -in rWgzqvey7wX-cable_news_network_inc.pem -noout -text:

            X509v3 Basic Constraints: critical
                CA:FALSE
            1.3.6.1.5.5.7.1.12:
image/svg+xml0#0!0...+............c<f.bb.JxWl..v8.0.......data:image/svg+xml;base64,H4sIAAAAAAAACp1UTW/bOBA9x7+Cq54KkDSHFL+MOEUbFE2BblFgAV8XrqLGwmptQ1bktL9+31AJ0kNQYAsk5HiGnHnz5lGXbx7+7cXUDqfusF9XpE0l2n1zuO32d+vqfvymUvXmanH5h1LiQ7tvh+14GFbi7e3hays+9v39aSwuYWuNy1L8tfkg3j8cD8MovvT3d+rjXuji3Mw1ViJoY8S7+66/Fea1EEoh/Wm6+xmErcTX7an9Mhy+dX27rsZu/10dT5XobtfVp+33dvibKgHk+9O62o3jcbVcns9nfXb6MNwtrTFmiZSPR1YPfbf/56WDlHNelmi1mLr2/O7wsK6MMCKHqP28liSr03HbAMhxaE/tMLUVMI/d2LdX158/Xy5nc3F53I47Acj9unp1fQ0UYBOI/4wxay+dddpONpCuGyPJ6FoFuClrUhS0lZZ0ahTpLI32yumEPSmvI++NirqWRpGHz+morFUE0j+FWMs6BZ0XF71CcqfIeD7vUIAPJ23x7xVZ3IracSRLQnFc1KHsebKGq/8CV9JOWqSCPyuDmI3IR047LuxxUGWgAIbAfvIvIkgzglqSZgQBCPIjAsocAgJ0AI4kJFUrXop1YxMKNZw9MQ3W4KKvGWMxeQX+Of7sl8U/2zug3nk7KZd12Clv0VQCBBz1GSxHppV8gknBcl8NyRTBQCQmxEeOzHFedwTObOPQmjQyYLoYCmxY6A0QJpVxES0hWnMG9GUlqwomCGtqkiEzAko8DCCdTSaZFhe4HzX97v1NEdxNEd+PavkLfVL0GG6d0H8DjiTYYE2hFpnI6U3pnFf0bFA44DwmBTgqzBsvG5cs6xBjYBnJwDMHEslgVZERVKQsJ85F5iRtSe148qxv/HA1Q0iIl1lCSeDAMus14/L8IHKQrDjsOqGeYz/OMmSeBgtNsprABJJBZ6rmfGAmzyBfgkhPEO0TxPQEMchZ6M8gjfSEnV+znB/cs6s4LMeYOyT+nwB5bjtWaZyI2+W60DxrQNFMeiwma0DVEBtEoObJ10XuRQ/c1/SkoN+7zQ+DStsYCV5ZUaHhZ2KgACrfJY4ZrLas0MVOhRJwzBsK43tVPioJf6dHU5ZfN875jYvlMS4uri0aDxK/eR7QJESIp+/kT/qchcxf96vFf2yTubu9BgAA
            1.3.6.1.4.1.11129.2.4.2:
                .z.x.v.UYS.0...l..R......(..V.B.U6.L_u.....].V~.....G0E. U..^.[!Ik..Z.....mJ..c................|..>;v.0.Ql.

Code language: PHP (php)

Note that 1.3.6.1.5.5.7.1.12 is a well-known OID and corresponds to the logotype, as defined back in 2004, and never changed by subsequent RFCs. It’s easy to see that, in the case of CNN, they have opted for embedding the logo directly on the certificate, but that’s not mandatory. However, OpenSSL, as said, does not know what fields are present and basically just dumps everything it gets (see, again, as reference the example presented by the before-mentioned IETF’s new proposal for logotypes to see what those bytes correspond to).

It still eludes me how exactly the SHA-256 hash is computed; my own trivial experiments resulted in completely different hashes. The RFC just says ‘a SHA-256 hash of the image’. But when using an ‘indirect’ reference (i.e. an URL to the image source), what exactly should be hashed? The URL itself? Or the retrieved SVG image? If the latter, is the hash taken before or after decompressing? (assuming that it’s a gzip-compressed SVG) Or does the hash include the MIME type as well?

1.3.6.1.4.1.11129.2.4.2 is substantially more cryptic, and oid-info.com only refers to it as ‘Rec. ITU-T X.509v3 certificate extension’. If it’s relevant or not, I cannot say, and I also have no idea what that data is supposed to be.


For the sake of completeness: basically, I want to get the answer to the email sent by Valentin Bud in January 2015 to the OpenSSL mailing list. He never got a reply.

He references an even earlier message, from November 2010, which was considerably hard to find. Fortunately, the OpenSSL team has an extensive archive (and some redundancy, too!) and although the ‘main’ archive only goes back to 2014, I found the thread Valentin references on a different archive server.

I feel I’m getting closer!


Okay — I was really, really, really very close. My solution was to manually create a whole OpenSSL configuration file (i.e. not only for the ‘arbitrary extension’), and point to the extension configuration I had found from Valentin Bud on the OpenSSL mailing list. It required some very minor tweaks, namely, that hashes, these days, should not use SHA1, but rather SHA256. After a few trials & errors, this is the configuration I managed to coerce OpenSSL to generate a perfectly valid CSR for me, using simply:

$ openssl req -new -config myrules.cfg -keyout myserver.key -out myserver.csr -nodes
Code language: Bash (bash)

where myrules.cfg is something like this:

[ req ]
default_bits            = 2048
default_keyfile         = keyfile.pem
distinguished_name      = req_distinguished_name
prompt                  = no
req_extensions          = extra_extensions
#oid_file                = extension.oid

[ req_distinguished_name ]
C                       = XY
ST                      = Some State Here
L                       = A City Name
O                       = My Server LLC
OU                      = My Server IT Department
CN                      = my-domain-name.example.com
emailAddress            = [email protected]

[ extra_extensions ]
# v3_ca
basicConstraints        = critical, CA:FALSE
# logotype_ext
1.3.6.1.5.5.7.1.12      = ASN1:SEQUENCE:logotype_ext

[ logotype_ext ]
issuerLogo      = EXPLICIT:1, IMPLICIT:1, SEQUENCE:logotype_indirect

[ logotype_indirect ]
refStructHash   = SEQWRAP, SEQUENCE:HashAlgAndValue
refStructURI    = SEQWRAP, IA5STRING:https://myserver.example.com/my-bimi-compliant.svg

[ HashAlgAndValue ]
hashAlg         = SEQUENCE:logo_algid
hashValue       = FORMAT:HEX, OCTETSTRING:4571c2e5aef5926c4426f294958b5510b812461bce9e22d1555ddb4330cbad95

[ logo_algid ]
capabilityID    = OID:sha256
parameter       = NULL
Code language: TOML, also INI (ini)

The OpenSSL CLI is happy, the CA I used for testing purposes is happy (although they stripped out the non-standard section and just signed the rest; now I have to find a different one, or use minica or something similar), and so I’m happy, too 🙂

Corrections and suggestions are most welcome; more specifically, it would be nice to understand how ASN.1 is ‘transpiled’ to the syntax of the OpenSSL ‘configuration file’…