Skip to content

Commit

Permalink
Add password option for pfx files
Browse files Browse the repository at this point in the history
Add an option for specifying a password for PFX files.
Add a test private key that has a password for use on macOS.
  • Loading branch information
martincostello committed Jun 9, 2019
1 parent 0683f71 commit a088f73
Show file tree
Hide file tree
Showing 10 changed files with 86 additions and 15 deletions.
4 changes: 4 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
###############################################################################
* text=auto

*.cer binary
*.p8 binary
*.pfx binary

###############################################################################
# Set default behavior for command prompt diff.
#
Expand Down
5 changes: 5 additions & 0 deletions src/AspNet.Security.OAuth.Apple/AppleAuthenticationOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ public new AppleAuthenticationEvents Events
/// </remarks>
public Func<string, Task<byte[]>> PrivateKeyBytes { get; set; }

/// <summary>
/// Gets or sets the password/passphrase associated with the private key, if any.
/// </summary>
public string PrivateKeyPassword { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the Team ID for your Apple Developer account.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public override async Task<string> GenerateAsync([NotNull] AppleGenerateClientSe
byte[] keyBlob = await _keyStore.LoadPrivateKeyAsync(context);
string clientSecret;

using (var algorithm = CreateAlgorithm(keyBlob))
using (var algorithm = CreateAlgorithm(keyBlob, context.Options.PrivateKeyPassword))
{
tokenDescriptor.SigningCredentials = CreateSigningCredentials(context.Options.KeyId, algorithm);

Expand All @@ -93,18 +93,19 @@ public override async Task<string> GenerateAsync([NotNull] AppleGenerateClientSe
return (clientSecret, expiresAt);
}

private ECDsa CreateAlgorithm(byte[] keyBlob)
private ECDsa CreateAlgorithm(byte[] keyBlob, string password)
{
// This becomes xplat in .NET Core 3.0: https://github.com/dotnet/corefx/pull/30271
return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
CreateAlgorithmWindows(keyBlob) :
CreateAlgorithmLinuxOrMac(keyBlob);
CreateAlgorithmLinuxOrMac(keyBlob, password);
}

private ECDsa CreateAlgorithmLinuxOrMac(byte[] keyBlob)
private ECDsa CreateAlgorithmLinuxOrMac(byte[] keyBlob, string password)
{
// Does not support .p8 files in .NET Core 2.x as-per https://github.com/dotnet/corefx/issues/18733#issuecomment-296723615
using (var cert = new X509Certificate2(keyBlob, string.Empty))
// Unlike Linux, macOS does not support empty passwords for .pfx files.
using (var cert = new X509Certificate2(keyBlob, password))
{
return cert.GetECDsaPrivateKey();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
Expand All @@ -21,11 +20,6 @@ namespace AspNet.Security.OAuth.Apple
{
public static class AppleClientSecretGeneratorTests
{
internal static readonly byte[] TestPrivateKey =
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
Convert.FromBase64String("MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgU208KCg/doqiSzsVF5sknVtYSgt8/3oiYGbvryIRrzSgCgYIKoZIzj0DAQehRANCAAQfrvDWizEnWAzB2Hx2r/NyvIBO6KGBDL7wkZoKnz4Sm4+1P1dhD9fVEhbsdoq9RKEf8dvzTOZMaC/iLqZFKSN6") :
Convert.FromBase64String("MIIEagIBAzCCBDAGCSqGSIb3DQEHAaCCBCEEggQdMIIEGTCCAw8GCSqGSIb3DQEHBqCCAwAwggL8AgEAMIIC9QYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIim9BBUiXkDgCAggAgIICyGrISvsUV1o7fVvskmnfWPQqScCIDx/P93fqnes0EwtQTMIaCFIR1i9TH/6NlrusAD9qHXE7W3i/3gNsiuMGC4cYim4ym8GBhyPxWJqcubSpOVv5adja5gmz7G1v6iB6H9mg2TNLR2RQx4dddjA4Q4vSXYp2DOrvGIB8Fw9Cjx5BrQYEL78IMzB3DmNtLoFs/ny7a0WjoxnyCeT8272rzOgiuPN4Fhtj+pJYorIeoEcyJ3DAvZur6CotHANYVhSETI8tUejX5oqpg2LuY5yzxRrgy81PbabWA92TvvNMy/3/WuFSO3RyhAj4+87+ru8q6cgNT3IcZ16YXMG+XQX9eyp1EwxeDe5he3/4FEfbQH0tMD7E3MAQUjUXI/wzhCCI5HvpLXtf0N4tc8b178ykc+1oZ3zpYs4mOEHjv/xcM27C6L5YeZD36MX0LHjUQGe7ezNSYewqMR8LRwqzZ6QpLY5Dv+izx297hcKuh2bvA7MatJuI+VlK0g7gTrKYp6OHaQH6Us8F7BlO+jI8AzippV8wsJKo1VwgMVPMku4ufKAdxTuSoJP55azbze5nEebzhOidazVMgttyPrKB8QzCa19iHEzqqjWEEvDerZt5K5Em7CVxnnNVwaS/Cm70/oe4W2hpS7D4Rrj7Q4pOK6paJhxa/RZGUWtaddCSVyvI8Er4aGI0xJy1rOXBEs2yBl9z30PxsPl2ddvCV2ax0E5semTDKkFhS35OHsiFAjLUyDc0tE/Erh2+u16rzk0ceKRpmK0kanCQtqS5Amd6zaPk/D+fOIZY5tHUATHX5OGfCpT4vfTFSnJX66IAeLBOKO67sveiXrFfZanxnHAEWc6a5xHCBPZvSKlGdCsm86rZR/tXESGLWEbO3T7X7L694Rtq37yg2HjW8SQ6Y5hzYWjjLjU2F5kBJj5q9UVGIuBRUucnT+8YPJ95/00wggECBgkqhkiG9w0BBwGggfQEgfEwge4wgesGCyqGSIb3DQEMCgECoIG0MIGxMBwGCiqGSIb3DQEMAQMwDgQICc6+CvVyhsMCAggABIGQWzkumOp4ivI9Y8uECZwnmhXGn2YoZyfBjH6LC1lBtjQC6qs5PcoeqmL0Ig/6ZyPGKZ56kZXLJfWv7hnLAGcBAgHhMrLl73XNxtiIdA/FVWZvNGrzETM9U5JpfhFOZvWgoAZDeGnOirrHjBOtihaCMBscel5ZmULjJXiIr5kiVfByYX+RBrSIjaAwWRQWfK6hMSUwIwYJKoZIhvcNAQkVMRYEFF7DK7P8nOIRMzRro6ajG9hRWl04MDEwITAJBgUrDgMCGgUABBTqJGte1FPTsA+57DXU5WGnb+NfiQQIZ5eHWqzCYQwCAggA");

[Fact]
public static async Task GenerateAsync_Generates_Valid_Signed_Jwt()
{
Expand All @@ -36,7 +30,8 @@ public static async Task GenerateAsync_Generates_Valid_Signed_Jwt()
ClientSecretExpiresAfter = TimeSpan.FromMinutes(1),
KeyId = "my-key-id",
TeamId = "my-team-id",
PrivateKeyBytes = (keyId) => Task.FromResult(TestPrivateKey),
PrivateKeyBytes = (keyId) => TestKeys.GetPrivateKeyBytesAsync(),
PrivateKeyPassword = TestKeys.GetPrivateKeyPassword(),
};

await GenerateTokenAsync(options, async (generator, context) =>
Expand Down Expand Up @@ -84,7 +79,8 @@ public static async Task GenerateAsync_Caches_Jwt_Until_Expired()
ClientSecretExpiresAfter = TimeSpan.FromSeconds(1),
KeyId = "my-key-id",
TeamId = "my-team-id",
PrivateKeyBytes = (keyId) => Task.FromResult(TestPrivateKey),
PrivateKeyBytes = (keyId) => TestKeys.GetPrivateKeyBytesAsync(),
PrivateKeyPassword = TestKeys.GetPrivateKeyPassword(),
};

await GenerateTokenAsync(options, async (generator, context) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,11 @@ void ConfigureServices(IServiceCollection services)
options.KeyId = "my-key-id";
options.TeamId = "my-team-id";
options.ValidateTokens = true;
options.PrivateKeyBytes = (keyId) =>
options.PrivateKeyPassword = TestKeys.GetPrivateKeyPassword();
options.PrivateKeyBytes = async (keyId) =>
{
Assert.Equal("my-key-id", keyId);
return Task.FromResult(AppleClientSecretGeneratorTests.TestPrivateKey);
return await TestKeys.GetPrivateKeyBytesAsync();
};
});
}
Expand Down
45 changes: 45 additions & 0 deletions test/AspNet.Security.OAuth.Providers.Tests/Apple/TestKeys.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
* for more information concerning the license and the contributors participating to this project.
*/

using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;

namespace AspNet.Security.OAuth.Apple
{
internal static class TestKeys
{
private static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);

internal static async Task<byte[]> GetPrivateKeyBytesAsync()
{
byte[] privateKey;

if (IsWindows)
{
string content = await File.ReadAllTextAsync(Path.Combine("Apple", "test.p8"));

if (content.StartsWith("-----BEGIN PRIVATE KEY-----", StringComparison.Ordinal))
{
string[] keyLines = content.Split('\n');
content = string.Join(string.Empty, keyLines.Skip(1).Take(keyLines.Length - 2));
}

privateKey = Convert.FromBase64String(content);
}
else
{
privateKey = await File.ReadAllBytesAsync(Path.Combine("Apple", "test.pfx"));
}

return privateKey;
}

internal static string GetPrivateKeyPassword() => IsWindows ? string.Empty : "passw0rd";
}
}
15 changes: 15 additions & 0 deletions test/AspNet.Security.OAuth.Providers.Tests/Apple/test.cer
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-----BEGIN CERTIFICATE-----
MIICYzCCAgmgAwIBAgIJAI8YasGI5F6oMAoGCCqGSM49BAMCMIGNMQswCQYDVQQG
EwJHQjEQMA4GA1UECAwHRW5nbGFuZDEPMA0GA1UEBwwGTG9uZG9uMRcwFQYDVQQK
DA5hc3BuZXQtY29udHJpYjEYMBYGA1UEAwwPTWFydGluIENvc3RlbGxvMSgwJgYJ
KoZIhvcNAQkBFhltYXJ0aW5AbWFydGluY29zdGVsbG8uY29tMB4XDTE5MDYwOTEy
NTgyNVoXDTE5MDcwOTEyNTgyNVowgY0xCzAJBgNVBAYTAkdCMRAwDgYDVQQIDAdF
bmdsYW5kMQ8wDQYDVQQHDAZMb25kb24xFzAVBgNVBAoMDmFzcG5ldC1jb250cmli
MRgwFgYDVQQDDA9NYXJ0aW4gQ29zdGVsbG8xKDAmBgkqhkiG9w0BCQEWGW1hcnRp
bkBtYXJ0aW5jb3N0ZWxsby5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQf
rvDWizEnWAzB2Hx2r/NyvIBO6KGBDL7wkZoKnz4Sm4+1P1dhD9fVEhbsdoq9RKEf
8dvzTOZMaC/iLqZFKSN6o1AwTjAdBgNVHQ4EFgQUZT6ij94pUE1x0rrWWCwswodX
FRMwHwYDVR0jBBgwFoAUZT6ij94pUE1x0rrWWCwswodXFRMwDAYDVR0TBAUwAwEB
/zAKBggqhkjOPQQDAgNIADBFAiAOR5YjLHk5QFPdq9BflblpV4/gwwYcxLM+OuNf
a4PsUQIhAJhh1DNlUH0kvG6/L1gXtd+pi41tjM96RVi4kvrf0xzN
-----END CERTIFICATE-----
3 changes: 3 additions & 0 deletions test/AspNet.Security.OAuth.Providers.Tests/Apple/test.p8
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-----BEGIN PRIVATE KEY-----
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgU208KCg/doqiSzsVF5sknVtYSgt8/3oiYGbvryIRrzSgCgYIKoZIzj0DAQehRANCAAQfrvDWizEnWAzB2Hx2r/NyvIBO6KGBDL7wkZoKnz4Sm4+1P1dhD9fVEhbsdoq9RKEf8dvzTOZMaC/iLqZFKSN6
-----END PRIVATE KEY-----
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
</PropertyGroup>
<ItemGroup>
<Content Include="xunit.runner.json;**\bundle.json" Exclude="bin\**\bundle.json" CopyToOutputDirectory="PreserveNewest" />
<Content Include="Apple\test.p8;Apple\test.pfx" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\AspNet.Security.OAuth.*\*.csproj" />
Expand Down

0 comments on commit a088f73

Please sign in to comment.