Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Import PEM keys #34086

Merged
merged 20 commits into from
Apr 9, 2020
Merged

Import PEM keys #34086

merged 20 commits into from
Apr 9, 2020

Conversation

vcsjones
Copy link
Member

@vcsjones vcsjones commented Mar 25, 2020

Closes #31201

@Dotnet-GitSync-Bot
Copy link
Collaborator

Note regarding the new-api-needs-documentation label:

This serves as a reminder for when your PR is modifying a ref *.cs file and adding/modifying public APIs, to please make sure the API implementation in the src *.cs file is documented with triple slash comments, so the PR reviewers can sign off that change.

@vcsjones

This comment has been minimized.

This is not strictly related to processing PEMs since the underlying
import methods already test this.
@vcsjones

This comment has been minimized.

@vcsjones
Copy link
Member Author

@bartonjs this is ready for review when ever you have time to spare.

}

Debug.Assert(bytesWritten == base64size);
Span<byte> decodedBase64 = decodeBuffer.AsSpan(0, base64size);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In bizarro-world, where TryFromBase64Chars reports a different answer in a release build, let's use the answer it gave.

Suggested change
Span<byte> decodedBase64 = decodeBuffer.AsSpan(0, base64size);
Span<byte> decodedBase64 = decodeBuffer.AsSpan(0, bytesWritten);

foundFields = fields;
foundSlice = pem;
}
else if (label.SequenceEqual("ENCRYPTED PRIVATE KEY"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if we need a PemHeaders constants class; but at least using a const string in this class would be better than repeating the literal across methods.


Debug.Assert(bytesWritten == base64size);
Span<byte> decodedBase64 = decodeBuffer.AsSpan(0, base64size);
importAction(decodedBase64, out _);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume that you've made a conscious choice to say it's OK to ignore trailing bytes here, since there's no check that the import action reports bytesRead == base64Size.

I can accept that... but a comment saying so would be nice. (Reasonable justification includes that the key has already been modified, so throwing and not have the same object modification effect)

[Fact]
public void ImportFromPem_Pkcs8_Simple()
{
using TAlg key = CreateKey();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use the braced using statement anywhere a using statement is used.

Isuk92Ner/JmgKjYoSumHVmSNfZ9nLTVjxeD08pD548KWrqmJAeZNsDDqQ==
-----END PUBLIC KEY-----";
using TAlg key = CreateKey();
AssertExtensions.Throws<ArgumentException>("input", () => key.ImportFromPem(pem));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see a test that says what happens to this input when sent to the encrypted PEM import. (Either success, or still throwing). (Perhaps I'm just bad at searching/remembering)

Comment on lines 84 to 92
<data name="Argument_PemImport_NoPemFound" xml:space="preserve">
<value>A supported key was not found.</value>
</data>
<data name="Argument_PemImport_AmbiguousPem" xml:space="preserve">
<value>Multiple supported keys were found.</value>
</data>
<data name="Argument_PemImport_EncryptedPem" xml:space="preserve">
<value>An encrypted key was found, but attempted to import it without a password.</value>
</data>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some user friendliness enhancement suggestions:

Suggested change
<data name="Argument_PemImport_NoPemFound" xml:space="preserve">
<value>A supported key was not found.</value>
</data>
<data name="Argument_PemImport_AmbiguousPem" xml:space="preserve">
<value>Multiple supported keys were found.</value>
</data>
<data name="Argument_PemImport_EncryptedPem" xml:space="preserve">
<value>An encrypted key was found, but attempted to import it without a password.</value>
</data>
<data name="Argument_PemImport_NoPemFound" xml:space="preserve">
<value>No supported key formats were found. Check that the input represents the contents of a PEM-encoded key file, not the path to such a file.</value>
</data>
<data name="Argument_PemImport_AmbiguousPem" xml:space="preserve">
<value>The input contains multiple keys, but only one key can be imported.</value>
</data>
<data name="Argument_PemImport_EncryptedPem" xml:space="preserve">
<value>An encrypted key was found, but no password was provided. Use ImportFromEncryptedPem to import this key.</value>
</data>

/// </summary>
/// <param name="input">The PEM text of the key to import.</param>
/// <exception cref="ArgumentException">
/// <paramref name="input"/> does not contain a PEM-encoded key with a recognized label.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe once one para is being used everything needs to be para-ified.

<exception cref="ArgumentException">
  <para><paramref name="input"/> does not contain a PEM-encoded key with a recognized label.</para>
  <para>-or</para>
  ...
</exception>

/// <remarks>
/// <para>
/// Unsupported or malformed PEM-encoded objects will be ignored. If multiple supported PEM labels
/// are found, an exception is raised to prevent importing a key when
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// are found, an exception is raised to prevent importing a key when
/// are found, an exception is thrown to prevent importing a key when

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems it is going to take a while longer for Ruby part of my career path to wear off.

/// When the base-64 decoded contents of <paramref name="input" /> indicate an algorithm that uses PBKDF1
/// (Password-Based Key Derivation Function 1) or PBKDF2 (Password-Based Key Derivation Function 2),
/// the password is converted to bytes via the UTF-8 encoding. This method
/// only supports the binary (BER/CER/DER) encoding of EncryptedPrivateKeyInfo.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that this method only supports the PEM encoded version of EncryptedPrivateKeyInfo 😄.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤦‍♂

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I recall what I was trying to communicate - it just came out poorly. I was trying to say that the base-64 contents, when decoded, must be a BER, DER, CER {Encrypted}PrivateKeyInfo.

{
return ImportPkcs8PrivateKey;
}
else if (label.SequenceEqual("PUBLIC KEY"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since these literals are spread out across multiple files, I think we should go ahead and create a PemConstants/PemHeaders/PemLabels constants file.

@vcsjones
Copy link
Member Author

vcsjones commented Apr 7, 2020

@bartonjs I think this addresses all of your feedback. The mono/linux/X509Certificates failure is unrelated.

@bartonjs bartonjs merged commit 29df078 into dotnet:master Apr 9, 2020
@vcsjones vcsjones deleted the 31201-impl branch April 9, 2020 16:23
@ghost ghost locked as resolved and limited conversation to collaborators Dec 10, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

API Proposal: Import keys from RFC7468 PEM
3 participants