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

[WIP] "Sign in With Apple" provider #318

Draft
wants to merge 19 commits into
base: dev
from
Draft
Changes from 1 commit
Commits
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.

Always

Just for now

Add password option for pfx files

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
commit a088f736c2ebc2314f93195cd61f14aadb946573
@@ -3,6 +3,10 @@
###############################################################################
* text=auto

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

###############################################################################
# Set default behavior for command prompt diff.
#
@@ -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>
@@ -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);

@@ -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();
}
@@ -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;
@@ -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()
{
@@ -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) =>
@@ -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) =>
@@ -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();
};
});
}
@@ -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";
}
}
@@ -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-----
@@ -0,0 +1,3 @@
-----BEGIN PRIVATE KEY-----
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgU208KCg/doqiSzsVF5sknVtYSgt8/3oiYGbvryIRrzSgCgYIKoZIzj0DAQehRANCAAQfrvDWizEnWAzB2Hx2r/NyvIBO6KGBDL7wkZoKnz4Sm4+1P1dhD9fVEhbsdoq9RKEf8dvzTOZMaC/iLqZFKSN6
-----END PRIVATE KEY-----
Binary file not shown.
@@ -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" />
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.