0

I have this code working fine. I am signing with an USB eToken. But after copying and pasting the PEM output of this code in the https://lapo.it/asn1js/ the trust chain is not shown. This eToken was provided by a CA and thus it has a trust chain of the signature. What's wrong?

lib = pkcs11.lib('/usr/lib/libeToken.so.9')

for slot in lib.get_slots():
    try:
        token = slot.get_token()
        with token.open(user_pin='****') as session:
        priv = session.get_key(object_class=pkcs11.constants.ObjectClass.PRIVATE_KEY)
        pub = session.get_key(object_class=pkcs11.constants.ObjectClass.PUBLIC_KEY)

        tbs = TbsCertificate({
            'version': 'v1',
            'serial_number': 1,
            'issuer': Name.build({
                'common_name': 'Test Certificate',
            }),
            'subject': Name.build({
                'common_name': 'Test Certificate',
            }),
            'signature': {
                'algorithm': 'sha256_rsa',
                'parameters': None,
            },
            'validity': {
                'not_before': Time({
                    'utc_time': datetime.datetime(2017, 1, 1, 0, 0),
                }),
                'not_after': Time({
                    'utc_time': datetime.datetime(2038, 12, 31, 23, 59),
                }),
            },
            'subject_public_key_info': {
                'algorithm': {
                    'algorithm': 'rsa',
                    'parameters': None,
                },
                'public_key': RSAPublicKey.load(encode_rsa_public_key(pub)),
            }
        })

        # Sign the TBS Certificate
        value = priv.sign(tbs.dump(),
                          mechanism=Mechanism.SHA256_RSA_PKCS)

        cert = Certificate({
            'tbs_certificate': tbs,
            'signature_algorithm': {
                'algorithm': 'sha256_rsa',
                'parameters': None,
            },
            'signature_value': value,
        })
        print(pem.armor('CERTIFICATE', cert.dump()).decode())
except TokenNotPresent:
    pass

1 Answers1

0

You have constructed and signed one individual X.509 certificate and then output it in PEM format. A chain of trust is multiple certificates, commonly provided as a list of PEM-encoded certificates starting from the leaf.

Thus you need to output the signing certificate as well. In X.509 there are two pieces of information: your public certificate (including public key) signed by the issuer and the private key you have used on your token.

PKCS#11 devices can store X.509 certificates so there's a good chance the signed X.509 object for this certificate is on your token and you can retrieve it with Session.get_objects.

# Retrieve first certificate object from the HSM
cert = next(session.get_objects({Attribute.CLASS: ObjectClass.CERTIFICATE}))
# Retrieve the DER-encoded value of the certificate
der_bytes = cert[Attribute.VALUE]
# Convert to PEM encoding
pem_bytes = pem.armor('CERTIFICATE', der_bytes)

This example is from Exporting Certificates.

If you have multiple certificates on your token you can include additional search parameters including the certificate type, issuer, etc. The docs contain more information on the parameters for certificate objects. The PKCS#11 spec contains further information still.

Alternatively, if you have the X.509 certificate in some other form you can simply append it. It does not need to be stored in the HSM.

Danielle Madeley
  • 2,616
  • 1
  • 19
  • 26
  • Thank you a lot! It worked like a charm. The eToken has 3 pieces. Rsa private and public keys, and the certificate, which is the trust chain, right? So, I tried to create a file.pem with the created certificate appended to certificate from token. So, the file.pem has become something like this: ----BEGIN CERTIFICATE----- ... ----END CERTIFICATE---- ----BEGIN CERTIFICATE----- ... ----END CERTIFICATE---- I tried to join then in the same BEGIN/END block. In both cases, the https://lapo.it/asn1j shows only the attributes from the token certificate. I'm a bit confused about this – Fellipe Theophilo Barata May 19 '18 at 17:52
  • For some reason, if I merge two pem files into a just one file, the ASN.1 decoders just read the first BEGIN END block. It's weird. – Fellipe Theophilo Barata May 19 '18 at 23:08
  • Hi danni, I've did some tests comparing a letsencrypt fullchain certificate with my combined fresh new certificate + eToken exported certificate. They two are pem encoded file. In both cases, the command openssl -inform pem -in file.pem -text outputs just the first block BEGIN/END. So, I'm thinking my combined PEM is alright. But, my main question is WHY or HOW this file: https://cie.sopagomeia.com.br/atributos/ws/EEA_ESTACIO/3JlKVSJ7?download seems to be fully showed in https://lapo.it/asn1js/ ? I tried to convert this binary file to PEM, no success. Hope you can give me a tip. – Fellipe Theophilo Barata May 20 '18 at 19:41
  • X.509 defines an ASN.1 structure for a single certificate, which can be encoded into DER form and from there into PEM. You want to keep the individual PEM blocks separate. PEM is base64 encoded DER. By removing the BEGIN and END block from the individual certificates the base64 decoder won't see the second certificate, it'll just look like nonsense). Each certificate is its own block of ASN.1. I'm not sure what file you've got there: it's ASN.1 but not an X.509 certificate. It could be the TBS section of the certificate: `openssl x509 -in 3JlKVSJ7 -inform der -noout` – Danielle Madeley May 20 '18 at 23:51
  • Hi danni. Let's forget the file from cie.sopagomeia.com.br. It was generated by some company, which is not me. So, I already understood that each certificate must be in it's own BEGIN/END block. But as I said before, I've compared a fullchain.pem from letsencrypt with my file.pem, which has the generated certificate with asn1crypto + pkcs11 certificate. – Fellipe Theophilo Barata May 21 '18 at 00:55
  • In both cases, the openssl command or https://lapo.it/asn1js/ only read the block that comes first. Why does it happens? I was expecting the output being all the BEGIN/END blocks. The weird file from cie.sopagomeia.com.br is a CRT somewhat binary and when it's uploaded to https://lapo.it/asn1js/, it seems that the output covers (I think) all the BEGIN/END blocks. I'm asking it because I plan to create a little software that makes the validation of the certificate and it's chain of trust. Thank you so much again. – Fellipe Theophilo Barata May 21 '18 at 00:55
  • Reading trust chains is not covered by X.509. Again single certificates are all that can be encoded in a given ASN.1 structure. Chains of certificates are covered by a number of RFCs e.g. [RFC4158](https://www.ietf.org/rfc/rfc4158.txt). – Danielle Madeley May 21 '18 at 01:03
  • Right. So, concatenate the certificate I built + pkcs11 certificate is alright then? There is nothing else to be done. Is it correct? – Fellipe Theophilo Barata May 21 '18 at 01:17
  • Correct. You can feed the final concatenated PEM file into something like [this](https://tools.keycdn.com/ssl). – Danielle Madeley May 21 '18 at 02:06