From a088f736c2ebc2314f93195cd61f14aadb946573 Mon Sep 17 00:00:00 2001 From: martincostello Date: Sun, 9 Jun 2019 14:03:15 +0100 Subject: [PATCH] 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. --- .gitattributes | 4 ++ .../AppleAuthenticationOptions.cs | 5 ++ .../DefaultAppleClientSecretGenerator.cs | 11 +++-- .../Apple/AppleClientSecretGeneratorTests.cs | 12 ++--- .../Apple/AppleTests.cs | 5 +- .../Apple/TestKeys.cs | 45 ++++++++++++++++++ .../Apple/test.cer | 15 ++++++ .../Apple/test.p8 | 3 ++ .../Apple/test.pfx | Bin 0 -> 1126 bytes ...pNet.Security.OAuth.Providers.Tests.csproj | 1 + 10 files changed, 86 insertions(+), 15 deletions(-) create mode 100644 test/AspNet.Security.OAuth.Providers.Tests/Apple/TestKeys.cs create mode 100644 test/AspNet.Security.OAuth.Providers.Tests/Apple/test.cer create mode 100644 test/AspNet.Security.OAuth.Providers.Tests/Apple/test.p8 create mode 100644 test/AspNet.Security.OAuth.Providers.Tests/Apple/test.pfx diff --git a/.gitattributes b/.gitattributes index 8a92b1213..10b136d73 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,6 +3,10 @@ ############################################################################### * text=auto +*.cer binary +*.p8 binary +*.pfx binary + ############################################################################### # Set default behavior for command prompt diff. # diff --git a/src/AspNet.Security.OAuth.Apple/AppleAuthenticationOptions.cs b/src/AspNet.Security.OAuth.Apple/AppleAuthenticationOptions.cs index 0f81120ee..8ce0ac49e 100644 --- a/src/AspNet.Security.OAuth.Apple/AppleAuthenticationOptions.cs +++ b/src/AspNet.Security.OAuth.Apple/AppleAuthenticationOptions.cs @@ -76,6 +76,11 @@ public new AppleAuthenticationEvents Events /// public Func> PrivateKeyBytes { get; set; } + /// + /// Gets or sets the password/passphrase associated with the private key, if any. + /// + public string PrivateKeyPassword { get; set; } = string.Empty; + /// /// Gets or sets the Team ID for your Apple Developer account. /// diff --git a/src/AspNet.Security.OAuth.Apple/Internal/DefaultAppleClientSecretGenerator.cs b/src/AspNet.Security.OAuth.Apple/Internal/DefaultAppleClientSecretGenerator.cs index 384228eb5..3486a3430 100644 --- a/src/AspNet.Security.OAuth.Apple/Internal/DefaultAppleClientSecretGenerator.cs +++ b/src/AspNet.Security.OAuth.Apple/Internal/DefaultAppleClientSecretGenerator.cs @@ -81,7 +81,7 @@ public override async Task 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 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(); } diff --git a/test/AspNet.Security.OAuth.Providers.Tests/Apple/AppleClientSecretGeneratorTests.cs b/test/AspNet.Security.OAuth.Providers.Tests/Apple/AppleClientSecretGeneratorTests.cs index 5acdafd5d..1e574d170 100644 --- a/test/AspNet.Security.OAuth.Providers.Tests/Apple/AppleClientSecretGeneratorTests.cs +++ b/test/AspNet.Security.OAuth.Providers.Tests/Apple/AppleClientSecretGeneratorTests.cs @@ -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) => diff --git a/test/AspNet.Security.OAuth.Providers.Tests/Apple/AppleTests.cs b/test/AspNet.Security.OAuth.Providers.Tests/Apple/AppleTests.cs index a43e9632e..60d827a22 100644 --- a/test/AspNet.Security.OAuth.Providers.Tests/Apple/AppleTests.cs +++ b/test/AspNet.Security.OAuth.Providers.Tests/Apple/AppleTests.cs @@ -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(); }; }); } diff --git a/test/AspNet.Security.OAuth.Providers.Tests/Apple/TestKeys.cs b/test/AspNet.Security.OAuth.Providers.Tests/Apple/TestKeys.cs new file mode 100644 index 000000000..9f3c3a4b2 --- /dev/null +++ b/test/AspNet.Security.OAuth.Providers.Tests/Apple/TestKeys.cs @@ -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 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"; + } +} diff --git a/test/AspNet.Security.OAuth.Providers.Tests/Apple/test.cer b/test/AspNet.Security.OAuth.Providers.Tests/Apple/test.cer new file mode 100644 index 000000000..9f67664d6 --- /dev/null +++ b/test/AspNet.Security.OAuth.Providers.Tests/Apple/test.cer @@ -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----- diff --git a/test/AspNet.Security.OAuth.Providers.Tests/Apple/test.p8 b/test/AspNet.Security.OAuth.Providers.Tests/Apple/test.p8 new file mode 100644 index 000000000..2e9721225 --- /dev/null +++ b/test/AspNet.Security.OAuth.Providers.Tests/Apple/test.p8 @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgU208KCg/doqiSzsVF5sknVtYSgt8/3oiYGbvryIRrzSgCgYIKoZIzj0DAQehRANCAAQfrvDWizEnWAzB2Hx2r/NyvIBO6KGBDL7wkZoKnz4Sm4+1P1dhD9fVEhbsdoq9RKEf8dvzTOZMaC/iLqZFKSN6 +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/test/AspNet.Security.OAuth.Providers.Tests/Apple/test.pfx b/test/AspNet.Security.OAuth.Providers.Tests/Apple/test.pfx new file mode 100644 index 0000000000000000000000000000000000000000..51ed10eb84f8434ffaacebb5ff8fe764a878afdf GIT binary patch literal 1126 zcmXqLVo73RWHxAG(O~1$YV&CO&dbQoxS)welBJ176euic(8SD+kYZcV#Pq|UiRlXy zBLk3ni;!V7kYVG3n!v-zX28e7@qM$ja?bDQY9=NQhK44l1AE>sn(}^*p3JGE)ee<1 z#rv`*`(*yD;&nFcd&YM9$5W0up(m<)+qUKM{Bua%y-m602j_FYU2~6GxzD}F%CL8( z_JL{*;RmM#E{kzWJ_A$u*-R^d6a&&!3-L2|g8-t?q z9~OqUk{a(^6bStK_lTogfz8PWtWQI%12&i+EN(D1Rc!NF8IkE7^7j4f+tE$`H#{_u z=-V0}`nmI|@T2OdlPj+Cc?PrSx&FA-D<)ZcWzvPwk%)Ey}KU%(am4$9=c)q z+o{)1J&=>Sutt7ait$IMgvS#(o;?0{?2Bmf+p~FBrI-KP{GMs&&9n(DhTJ{^zkRtx z44>CF)<~Y3mT~;q>YUq-omU^|dzrrTaO>%J6LDG+6EJUj`5E;&#>^XRm0M~#b{|~z zQ+TiZt<9yAcSx5cKX~SLrf#EXVUEkCrMf3FvuA%56;@%j5H3o%tA5UB)3*J`zousX z*6r38G!WjuB13Dbv%~j!PfC}`R10*;B+N9+d*5`Gb;1R6o-Ov-@+VU-yW8j^wl_J{yXE-c`)| zXb~`Z&fl|9X5ocbKb8hwy~MX%CsxyJmqepYLiDy);b%v3r0Rn&)n8DYYuM=XUh{)N z6C)E+f?v@1g{ASMLE}4v#@B4zu%yq$$h4qwi$UW?l>ER9$`9AXl-r)));$i&4=jxn zmdmeeEPfpG-bo^a@&D%}?T$xJI}HzU&6}2`pwF;ZK3Lb(zE?qnuRO{5@1#Aoaj(iH zxc?>!teM+(-0A<#?b8^gkLvrD-0x%akUjnA(~A}sHiZ!N#0_s2u2^>>>dzwe85d^z z{H$B{>QIC4nh%Or59X$MsQnEUlb$*8 z!@b8TYAD7c^4ZVpjPpA8W3%@OKeL=TdvWwLhaLk%14RQ)HdbvuW+o|C1{RU$VS1vk hG&e3&&YpFFT`-_KXHljG3&%ITioKfVRXQNg0s!Js`*Z*R literal 0 HcmV?d00001 diff --git a/test/AspNet.Security.OAuth.Providers.Tests/AspNet.Security.OAuth.Providers.Tests.csproj b/test/AspNet.Security.OAuth.Providers.Tests/AspNet.Security.OAuth.Providers.Tests.csproj index 6a3f0dcd6..11b34686b 100644 --- a/test/AspNet.Security.OAuth.Providers.Tests/AspNet.Security.OAuth.Providers.Tests.csproj +++ b/test/AspNet.Security.OAuth.Providers.Tests/AspNet.Security.OAuth.Providers.Tests.csproj @@ -9,6 +9,7 @@ +