Runtime: Create an API to manipulate PKCS12 objects

Created on 20 Mar 2018  Â·  8Comments  Â·  Source: dotnet/runtime

https://tools.ietf.org/html/rfc7292

  • Allow enumeration of contents by type (KeyBag, PKCS8ShroudedKeyBag, CertBag, CRLBag, SecretBag)

    • SafeContents is a recursive node, so maybe it needs to be part of the enumerator and not an enumeratable thing. Or maybe it should also be enumeratable because maybe someone wants to indicate "these belong together" for its contents.

  • Should allow the authenticity and integrity passwords to be different (in password mode)
  • Technically, the integrity mode can also be disabled.
  • P2: Support for public-key modes

    • P3: Support for { public key integrity, password authenticity } and { password integrity, public key authenticity }

  • Virtuous: A method which, given an X509Certificate2 where HasPrivateKey=true adds the key and the cert and used an automatic cert/key relationship identifier.

    • Windows-style would be to add the key to a shrouded key bag, the cert to be added to a CertBag which gets encrypted, and the key identifier to be a sequence number.

    • Windows also would carry the persisted key name (as "friendly name") and an indicator of the CSP/KSP, and (if appropriate) an indication if the key was a machine key or user key.



      • Need to decide what parts of these to do by default



  • Each added item needs to be capable of having properties defined on it. (And each enumerated item capable of having the properties inspected)

Gotchas:

  • The spec allows for keys to be "shrouded" (PKCS8 encrypted), unencrypted KeyBag, or encrypted KeyBag. Need a way to distinguish user intent.
  • The spec allows for unencrypted and encrypted CertBags. So we need a way to distinguish user intent.
  • Technically speaking, all other bags have this same conundrum. Only private keys are tri-state, though.

Algorithms:

  • Windows 7 only supports the legacy PBES1 options.
  • We definitely need to allow user expression of algorithm. Should we require it? Or have a default of "What Windows 7 does"?

Design:

  • Maybe "build" and "read" don't need to be the same class?

    • If builder is separate, does it need "append"?

  • For a separate builder, it might be easiest to require the passwords/keys at construction time, and then have an API which is AddEncryptedContents(SafeBag) and AddUnencryptedContents(SafeBag).

    • Or maybe SafeBags should have a property of whether they are (reader) or should-be (builder) encrypted.

API Proposal

Discussion points

  • The AuthenticatedSafe property: This name comes from a type name in the RFC (the field name is authSafe), and an AuthenticatedSafe is a collection of SafeContents.

    • Other libraries:

    • BouncyCastle calls it ContentInfos

    • OpenSSL doesn't refer to the field, but has PKCS12_unpack_authsafes (plural) to read it, suggesting a name of AuthSafe in lieu of SafeContents.

    • Alternative Suggestions:

    • Rename Pkcs12SafeContents to [Pkcs12]AuthSafe, rename property to Auth[enticated]Safes

  • The prefix Pkcs12 on types: Would it be clearer (though somewhat inaccurate) to use Pfx instead?
  • Should all of the types other than the attribute be put into a Pkcs12 sub-namespace?
  • Should the two nested enums be promoted out?
  • Should the PublicKey enum members be removed until there is support?
  • Pkcs12Builder.AddSafeContentsUnencrypted can accept an encrypted SafeContents and will succeed (because no further encryption is required). Is there a better name which still helps callers be aware that a SafeContents they just built is not going to be encrypted?

Exception Model

  • Pkcs12Info.VerifyMac will return false when there is no MAC to verify (alternatively, InvalidOperationException)
  • ArgumentNullException in all the expected places.
  • InvalidOperationException:

    • Pkcs12Builder

    • IsSealed == false



      • Encode


      • TryEncode



    • IsSealed == true



      • Seal*


      • Add*



    • Pkcs12SafeContents:

    • IsReadOnly == true



      • Add*



    • DataConfidentialityMode != None



      • GetBags



    • DataConfidentialityMode != Password



      • Decrypt



  • CryptographicException:

    • Decode fails, Decrypt fails, Encode catches something late (like illegal OID values).

    • Pretty much every exception aside from the ones above.

Contract Proposal

```C#
namespace System.Security.Cryptography.Pkcs
{
public sealed partial class Pkcs9LocalKeyId : Pkcs9AttributeObject
{
public ReadOnlyMemory KeyId { get; }
public Pkcs9LocalKeyId() => throw null;
public Pkcs9LocalKeyId(ReadOnlySpan keyId) => throw null;
}
public sealed partial class Pkcs12Builder
{
public bool IsSealed { get; }
public void AddSafeContentsEncrypted(Pkcs12SafeContents safeContents, ReadOnlySpan passwordBytes, PbeParameters pbeParameters) => throw null;
public void AddSafeContentsEncrypted(Pkcs12SafeContents safeContents, ReadOnlySpan password, PbeParameters pbeParameters) => throw null;
public void AddSafeContentsUnencrypted(Pkcs12SafeContents safeContents) => throw null;
public byte[] Encode() => throw null;
public void SealAndMac(ReadOnlySpan password, HashAlgorithmName hashAlgorithm, int iterationCount) => throw null;
public void SealWithoutIntegrity() => throw null;
public bool TryEncode(Span destination, out int bytesWritten) => throw null;
}
public sealed partial class Pkcs12Info
{
private Pkcs12Info() { }
public ReadOnlyCollection AuthenticatedSafe { get; }
public IntegrityMode DataIntegrityMode { get; }
public bool VerifyMac(ReadOnlySpan password) => throw null;
public static Pkcs12Info Decode(ReadOnlyMemory encodedBytes, out int bytesConsumed, bool skipCopy=false) => throw null;

    public enum IntegrityMode
    {
        Unknown = 0,
        None = 1,
        Password = 2,
        PublicKey = 3,
    }
}
public sealed partial class Pkcs12SafeContents
{
    public ConfidentialityMode DataConfidentialityMode { get; }
    public bool IsReadOnly { get; }
    public void AddSafeBag(Pkcs12SafeBag safeBag) => throw null;
    public CertBag AddCertificate(X509Certificate2 certificate) => throw null;
    public KeyBag AddKeyUnencrypted(AsymmetricAlgorithm key) => throw null;
    public ShroudedKeyBag AddShroudedKey(AsymmetricAlgorithm key, ReadOnlySpan<char> password, PbeParameters pbeParameters) => throw null;
    public ShroudedKeyBag AddShroudedKey(AsymmetricAlgorithm key, ReadOnlySpan<byte> password, PbeParameters pbeParameters) => throw null;
    public SecretBag AddSecret(Oid secretType, ReadOnlyMemory<byte> secretValue, bool skipCopy=false) => throw null;
    public void Decrypt(ReadOnlySpan<char> password) => throw null;
    public IEnumerable<Pkcs12SafeBag> GetBags() => throw null;
    public enum ConfidentialityMode
    {
        Unknown = 0,
        None = 1,
        Password = 2,
        PublicKey = 3,
    }
}
public abstract partial class Pkcs12SafeBag
{
    protected Pkcs12SafeBag(string bagIdValue) { }
    public CryptographicAttributeObjectCollection Attributes { get; }
    public byte[] Encode() => throw null;
    public Oid GetBagId() => throw null;
    public bool TryEncode(Span<byte> destination, out int bytesWritten) => throw null;
    protected abstract bool TryEncodeValue(Span<byte> destination, out int bytesWritten);
}
public sealed partial class CertBag : Pkcs12SafeBag
{
    public CertBag(Oid certificateType, ReadOnlyMemory<byte> encodedCertificate, bool skipCopy=false) : base(null) => throw null;
    public bool IsX509Certificate { get; }
    public ReadOnlyMemory<byte> EncodedCertificate { get; }
    public Oid GetCertificateType() => throw null;
    public X509Certificate2 GetCertificate() => throw null;
    protected override bool TryEncodeValue(Span<byte> destination, out int bytesWritten) => throw null;
}
public sealed partial class KeyBag : Pkcs12SafeBag
{
    public KeyBag(ReadOnlyMemory<byte> pkcs8PrivateKey, bool skipCopy=false) : base(null) { }
    public ReadOnlyMemory<byte> Pkcs8PrivateKey { get; }
    protected override bool TryEncodeValue(Span<byte> destination, out int bytesWritten) => throw null;
}
public sealed partial class SafeContentsBag : Pkcs12SafeBag
{
    private SafeContentsBag() : base(null) { }
    protected override bool TryEncodeValue(Span<byte> destination, out int bytesWritten) => throw null;
    public Pkcs12SafeContents SafeContents { get; }
    public static SafeContentsBag CreateEncrypted(Pkcs12SafeContents safeContents, ReadOnlySpan<byte> passwordBytes, PbeParameters pbeParameters) => throw null;
    public static SafeContentsBag CreateEncrypted(Pkcs12SafeContents safeContents, ReadOnlySpan<char> password, PbeParameters pbeParameters) => throw null;
    public static SafeContentsBag CreateUnencrypted(Pkcs12SafeContents contents) => throw null;
}
public sealed partial class SecretBag : Pkcs12SafeBag
{
    private SecretBag() : base(null) { }
    public Oid GetSecretType() => throw null;
    public ReadOnlyMemory<byte> SecretValue { get; }
    protected override bool TryEncodeValue(Span<byte> destination, out int bytesWritten) => throw null;
}
public sealed partial class ShroudedKeyBag : Pkcs12SafeBag
{
    public ShroudedKeyBag(ReadOnlyMemory<byte> encryptedPkcs8PrivateKey, bool skipCopy=false) : base(null) { }
    public ReadOnlyMemory<byte> EncryptedPkcs8PrivateKey { get; }
    protected override bool TryEncodeValue(Span<byte> destination, out int bytesWritten) => throw null;
}

}
```

api-approved area-System.Security enhancement

Most helpful comment

I tried to use this, and can't see the new interface. Here is what i have done:

  • Installed nuget package: System.Security.Cryptography.Pkcs -Version 4.6.0-preview6.19303.8
  • Installed Core 3.0 preview 7:
    -rwxrwxrwa 1 ORADEV\smckinle 125757656 Jul 30 13:30 dotnet-sdk-3.0.100-preview7-012821-win-x64.exe
    -rwxrwxrwa 1 ORADEV\smckinle 26124576 Jul 30 13:38 dotnet-runtime-3.0.0-preview7-27912-14-win-x64.exe

  • Installed VS 2019 Preview Version 16.3.0 Preview 1.0

  • Set the target framework to .NET Standard 2.1 in the project file
  • Set the global.json to:
    {
    "sdk": {
    "version": "3.0.100-preview7-012821"
    }
    }

The above got me to the point of being to set our Core library project to the "target framework" of .NET Standard 2.1 (ie Core 3). However, when i try to reference, eg, the new Pkcs12Info class, it isn't found.

Am i still missing something?

All 8 comments

This functionality is greatly needed. We specifically need access to the contents of the SecretBag, from a read only perspective.

Excellent! Our only must have feature here is read-only SecretBag contents access. Thanks for working on this.

@alexkeh a bit OT, but can I ask how you are actually adding SecretBags in the first place? I haven't found any .NET libraries that have this capability

@ScotMac can answer that question better than I can.

Hi, we are currently using rsa libraries to write the record, as part of
the config setup (ie not a runtime operation). We need the .NET Core api
for the runtime read only for our fully Managed DB Client.

Thanks Scot

On Apr 20, 2018 12:23 PM, "cocowalla" notifications@github.com wrote:

@alexkeh https://github.com/alexkeh a bit OT, but can I ask how you are
actually adding SecretBags in the first place? I haven't found any .NET
libraries that have this capability

—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/dotnet/corefx/issues/28249#issuecomment-383196880,
or mute the thread
https://github.com/notifications/unsubscribe-auth/Ae0eiTjI7vJ-QPODnHjcymQW05h0v2pJks5tqjWrgaJpZM4SySAl
.

As an example of the API, to take a Windows-generated PFX and remove the "FriendlyName" attribute from ShroudedKeyBags:

```C#
public static byte[] RemoveKeyNames(byte[] pfxBytes, string password)
{
Pkcs12Info info = Pkcs12Info.Decode(pfxBytes, out _, skipCopy: true);

if (info.DataIntegrityMode == Pkcs12Info.IntegrityMode.Password)
{
    if (!info.VerifyMac(password))
    {
        throw new CryptographicException();
    }
}

const string FriendlyNameOid = "1.2.840.113549.1.9.20";

Pkcs12Builder builder = new Pkcs12Builder();

foreach (Pkcs12SafeContents existingContents in info.AuthenticatedSafe)
{
    if (existingContents.DataConfidentialityMode != Pkcs12SafeContents.ConfidentialityMode.None)
    {
        builder.AddSafeContentsUnencrypted(existingContents);
        continue;
    }

    Pkcs12SafeContents newContents = new Pkcs12SafeContents();

    foreach (Pkcs12SafeBag safeBag in existingContents.GetBags())
    {
        foreach (CryptographicAttributeObject attrSet in safeBag.Attributes)
        {
            if (attrSet.Oid.Value == FriendlyNameOid)
            {
                safeBag.Attributes.Remove(attrSet);
                Console.WriteLine("Removing \"FriendlyName\" attribute");
                break;
            }
        }

        newContents.AddSafeBag(safeBag);
    }

    builder.AddSafeContentsUnencrypted(newContents);
}

builder.SealAndMac(password, HashAlgorithmName.SHA1, 2048);
return builder.Encode();

}
```

Looks good, a few comments:

  • Consider prefixing all types with Pkcs12 to avoid clashes
  • Change SealAndMac to SealWithMac
  • Make enums top-level types
  • Make sure parameter ROS<byte> passwordBytes is named consistently (it's not on Pkcs12SafeContents.AddShroudedKey)

Make sure that a language that doesn't have span support can use PKCS12.

  • Specifically, add an overload that accepts string next to ROS<char>.

I tried to use this, and can't see the new interface. Here is what i have done:

  • Installed nuget package: System.Security.Cryptography.Pkcs -Version 4.6.0-preview6.19303.8
  • Installed Core 3.0 preview 7:
    -rwxrwxrwa 1 ORADEV\smckinle 125757656 Jul 30 13:30 dotnet-sdk-3.0.100-preview7-012821-win-x64.exe
    -rwxrwxrwa 1 ORADEV\smckinle 26124576 Jul 30 13:38 dotnet-runtime-3.0.0-preview7-27912-14-win-x64.exe

  • Installed VS 2019 Preview Version 16.3.0 Preview 1.0

  • Set the target framework to .NET Standard 2.1 in the project file
  • Set the global.json to:
    {
    "sdk": {
    "version": "3.0.100-preview7-012821"
    }
    }

The above got me to the point of being to set our Core library project to the "target framework" of .NET Standard 2.1 (ie Core 3). However, when i try to reference, eg, the new Pkcs12Info class, it isn't found.

Am i still missing something?

Was this page helpful?
0 / 5 - 0 ratings