Yubico / Yubico.NET.SDK

A YubiKey SDK for .NET developers
Apache License 2.0
96 stars 48 forks source link

How to create PEM-encoded certificate request file? #6

Closed gnida-rada closed 2 years ago

gnida-rada commented 2 years ago

In yubico-piv-tool/ykman there is 1 liner command that creates PEM-encoded cert request file. In .NET SDK it is a lot more work and, at least for me, it does not seem to work.

yubico-piv-tool -a verify-pin -a request-certificate -s -i -S -o or: ykman piv certificates request -P -s

with .NET SDK I'm doing something like the below (based on Yubikey .NET SDK sample code). In the end I do get a PEM encoded file. It has a little different formatting than what ykpiv gives us on macOS:

get IYubiKeyDevice using AsymmetricAlgorithm dotNetPubKey = KeyConverter.GetDotNetFromPivPublicKey(publicKey); CertificateRequest certRequest = new CertificateRequest( subjectName, (RSA)dotNetPubKey, HashAlgorithmName.SHA256, // NOTE: I am using Pkcs1 instead of PSS here because that's the padding that ykpiv is using for RSA RSASignaturePadding.Pkcs1)

        using (var pivSession = new PivSession(yubiKey))
        {
            YubikeyPivKeyCollector keyCollector = new YubikeyPivKeyCollector();
            keyCollector.SetPinString(pinString);
            pivSession.KeyCollector = keyCollector.KeyCollectorDelegate;

            // convert string like "9c" to hex 0x9c
            byte slot = (byte)Convert.ToInt32(slotString, 16);

            var signer = new YubiKeySignatureGenerator(pivSession, slot, publicKey);
            byte[] signingRequestDer = certRequest.CreateSigningRequest(signer);

            char[] signingRequestPem = PemOperations.BuildPem(
                                            "CERTIFICATE REQUEST",
                                            signingRequestDer);

            if (signingRequestPem != null && outFilePath != null)
            {
                File.WriteAllBytes(outFilePath,
                                    System.Text.Encoding.UTF8.GetBytes(signingRequestPem));
            }

            return new string (signingRequestPem);
        }
gnida-rada commented 2 years ago

Ping. @GregDomzalski, would it be possible to get code samples that allow me to get attestation for a certificate using .NET SDK? Currently we do it using 2 commands: yubico-piv-tool -s $slot -P $UserPin --key=$mk -a verify -a request-certificate -S $CertificateSubjectName --input=$UserPublicKeyFilePath --output=$UserCertificateRequestFilePath followed by yubico-piv-tool --action=attest --slot=$slot --output=$UserAttestationFilePath

What are the equivalents? Thanks

GregDomzalski commented 2 years ago

The team should have some bandwidth next week to take a look at this. Thanks for your patience.

gnida-rada commented 2 years ago

So I ran openssl on the CSR files, generated both ways. And they seem to be very different. The one generated with command line tools looks like: Certificate Request: Data: Version: 0 (0x0) Subject: ... Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (2048 bit) Modulus: ... Exponent: 65537 (0x10001) Attributes: Requested Extensions: Signature Algorithm: sha256WithRSAEncryption ...

whereas the one, generated using CertificateRequest.CreateSigningRequest(new YubiKeySignatureGenerator(...)) looks like: Certificate Request: Data: Version: 0 (0x0) Subject: ... Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (2048 bit) Modulus: ... Exponent: 65537 (0x10001) Attributes: a0:00 Signature Algorithm: rsassaPss Hash Algorithm: sha256 Mask Algorithm: mgf1 with sha256 Salt Length: 0x20 Trailer Field: 0xBC (default) ...

GregDomzalski commented 2 years ago

I forwarded this thread to another one of our engineers. This is what he wrote:

It sounds like you are doing the following:

  1. Generate an RSA key pair on a YubiKey, say in slot 9C.
  2. Create an attestation statement for the key in the slot.
  3. Get a cert request for that public key. Use RSA with SHA-256 and PKCS #1v1.5 padding.

1: It seems that's no problem. You are able to generate a new key pair, right?

PivSession.GenerateKeyPair()

2: In the SDK, to create an attestation statement, use

PivSession.CreateAttestationStatement(byte slotNumber).

See the User's Manual entry for additional information on attestation,

https://docs.yubico.com/yesdk/users-manual/application-piv/attestation.html

This produces an X509Certificate2 object, a .NET class, so you'll have to look at its documentation to figure out how to get the data out in the form you want. Maybe the sample code PEM conversion code can help.

Note that you don't need to have a cert request built in order to create an attestation statement. Also, it is possible to attest keys only if they were generated by the YubiKey. If it was imported, it is not attestable.

3: I believe the solution you want is to set the RSA padding scheme in the YubiKeySignatureGenerator object as well as the CertificateRequest object.

In the sample code there is this switch statement.

            slotContents.CertRequest = slotContents.Algorithm switch
            {
                PivAlgorithm.EccP256 => new CertificateRequest(
                    distinguishedName,
                    (ECDsa)dotNetPubKey,
                    HashAlgorithmName.SHA256),
                PivAlgorithm.EccP384 => new CertificateRequest(
                    distinguishedName,
                    (ECDsa)dotNetPubKey,
                    HashAlgorithmName.SHA384),
                _ => new CertificateRequest(
                    distinguishedName,
                    (RSA)dotNetPubKey,
                    HashAlgorithmName.SHA256,
                    RSASignaturePadding.Pss),
            };

I believe you have changed that "Pss" to "Pkcs1".

                new CertificateRequest(
                    distinguishedName,
                    (RSA)dotNetPubKey,
                    HashAlgorithmName.SHA256,
                    RSASignaturePadding.Pkcs1),

Unfortunately, that is not the only place to change it. Change it in the YubiKeySignatureGenerator constructor as well. Here is the constructor in the sample code.

        public YubiKeySignatureGenerator(
            PivSession pivSession,
            byte slotNumber,
            PivPublicKey pivPublicKey,
            RSASignaturePaddingMode rsaPaddingMode = RSASignaturePaddingMode.Pss)

There is that fourth argument that has a default. In the code sample you sent, you are calling the constructor with only three arguments, so the padding scheme is the default PSS.

As it turns out, the .NET CertificateRequest class ignores the padding scheme you give it and uses whatever scheme is specified in the SignatureGenerator.

Is that the only issue you are having with the cert request (other than 76 vs. 64 characters per line and the new line characters)?

gnida-rada commented 2 years ago

Yes, indeed, I missed that default parameter! :flushed: After I added 4th param to invocation of YubiKeySignatureGenerator constructor, things worked. Thanks a lot @GregDomzalski and "another one of our engineers", for following up and spotting my silly mistake! You rock!