Skip to content
This repository has been archived by the owner on Mar 20, 2023. It is now read-only.

PemWriter creates an invalid PEM file when byte begin with a zero #29

Open
anupkattel opened this issue Apr 12, 2020 · 7 comments
Open

Comments

@anupkattel
Copy link

anupkattel commented Apr 12, 2020

Initially I found an intermittent issue in my integration tests with RSAParameters to PEM conversion using PemWriter and reading it back using PemReader.

Now I have narrowed it down to PemWriter and I can replicate it.

When any of the RSAParameters such as Modulus, D, P, etc start with zero as the first byte, PemWriter generates an invalid PEM file. It does not include the first zero in the PEM file.

I am looking further into this. I will post if I find anything interesting.

@anupkattel
Copy link
Author

anupkattel commented Apr 12, 2020

All initial zeros in the byte arrays of RSAParameters are lost when we convert them to big integers.

        public void WritePrivateKey(RSAParameters parameters)
        {
            var sequence = new DerAsnSequence(new DerAsnType[]
            {
                new DerAsnInteger(ToBigInteger(new byte[] { 0x00 })),   // Version
                new DerAsnInteger(ToBigInteger(parameters.Modulus)),
                new DerAsnInteger(ToBigInteger(parameters.Exponent)),
                new DerAsnInteger(ToBigInteger(parameters.D)),
                new DerAsnInteger(ToBigInteger(parameters.P)),
                new DerAsnInteger(ToBigInteger(parameters.Q)),
                new DerAsnInteger(ToBigInteger(parameters.DP)),
                new DerAsnInteger(ToBigInteger(parameters.DQ)),
                new DerAsnInteger(ToBigInteger(parameters.InverseQ))
            });

Unless we know the length of the array by some means such as knowing RSA Key size, I'm not sure how DerAsn can tell the real length.

@anupkattel
Copy link
Author

Turns out that the problem is not in the export, it's the import that doesn't work well.

I used .net core's inbuilt PKS#1 exporter and tried exporting a certificate that doesn't get exported back by PemUtils properly. The exported PEM file are exactly the same from both methods. So, it must be the import that is not taking care of the leading zero bytes.

@huysentruitw
Copy link
Owner

Thanks for reaching out. Can you post the RSA parameters you're testing with? Just to make sure we'll do the same testing...

@anupkattel
Copy link
Author

anupkattel commented Apr 12, 2020

Sure. I basically ran a loop 100 times and kept generating new RSAParameters and trying to export to PEM and import back until it failed. I noticed that every time it fails, one of the RSA parts starts with zero byte. For example, D or Private Exponent is like byte[] {0, .......}

Here's a valid PEM file that works on this website: https://8gwifi.org/PemParserFunctions.jsp

-----BEGIN RSA PRIVATE KEY-----
MIIBOgIBAAJBAM/tOjXpAjH/9npX23HbImf9ve8JWR4GH7MQze+RdtYYsSY++zQa
OAOzI8qlXIwsZ37dPLHmA4DmDQ8UmEHWTMECAwEAAQJAAKNAn3O0fwTC88MbODEs
3NbJY1dK/62TIVB24To3/BYb4ZwSGSPCmiqJm+rjpQT3SBS4O+sv5xuTDHvFUyOW
5QIhAPk05e5fFsUXKvAusue/6uTsyDVt1tn+fr+hUdgXMuC3AiEA1ZhBOSQotoUw
m3emmJNwQEg1vdsVwfg+WNFVdmkn1kcCIQDCf+TNbO3KmdBrcSc09XcRgTSpbrb3
oUevrOrB6ylMdQIgeCbXuc8PX+z4dNwPyRBXOrHkGVKeoKiGWewXyS5KDI0CIEZ9
Zx+Hyty+TgFVgqXkXCEBW6wj0IlojSeesHMw1AKj
-----END RSA PRIVATE KEY-----

If you try importing this certificate, you will see that the Private Exponent or D will be of 63 bytes instead of being 64 as usual (64 bytes because I generated a 512 bit key for simplicity)

@anupkattel
Copy link
Author

Here's another one where InverseQ or the coefficient starts with a zero:

-----BEGIN RSA PRIVATE KEY-----
MIIBOwIBAAJBALaKjfztjNBmquEGlPNLNG5fhoARa7D3vZ+lGHD7Yx7GdOjqPCw2
gmMg/2LrZ3mSqRVNMe8TdjOq5ymUAn7a0Y0CAwEAAQJBAK1xDORr2cYSJv7UwCZD
KLhOr390Df0CB/xuY8DGOzH7Bx6UTlb0wSIfNQT/i+zqStynBYDB9esCnXb3aRoV
xkkCIQDIq/fFd91n1HyFqLbJAXVyvR+WSV6va83hKVYElldvAwIhAOje4O4YPnaX
8tyfs+/XzclDm0adFU2SijLdAZUmidAvAiBgX0ZFDYXFQaTzw8zUx+CR1AYBdQcG
FC3xvppS5ajj5QIhALQ+Ps9LSJ22gq4tlrKf0JJll7wSPbjrIbi4w07Uo3rVAiAA
uDFv77OpPej7Jgi0xfYnJAtkrPG8PKghMe706MhUAw==
-----END RSA PRIVATE KEY-----

@huysentruitw
Copy link
Owner

In short, the 0x00 is used as padding in case the second byte is > 0x80 (sign-bit) so the number isn't being treated as a negative integer. However, sometimes the 0x00 should be stripped before putting the byte array into RSAParameters and in other cases the 0x00 must be kept, and I don't see when or why.

For reference, here is the openssl code that manages the ASN INTEGER type: https://github.com/openssl/openssl/blob/e7fb44e7c3f7a37ff83a6b69ba51a738e549bf5c/crypto/asn1/a_int.c

@awakecoding
Copy link

We're hitting the same problem, we've been wondering why some keys were not loading properly, and it turns out it's because of leading zeros being omitted. The quickest workaround is to generate and load keys until one of them loads properly, but we should really fix the encoding to encode leading zeros.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants