Skip to content

Commit 718b99f

Browse files
Support for Base64UrlEncoded certificates. (#1923)
1 parent 14ff90a commit 718b99f

9 files changed

Lines changed: 92 additions & 8 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ ClientBin/
229229
*.pfx
230230
*.publishsettings
231231
orleans.codegen.cs
232+
!**/Certificates/*.pfx
232233

233234
# Including strong name files can present a security risk
234235
# (https://github.com/github/gitignore/pull/2483#issue-259490424)

build/credscan-exclusion.json

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,14 @@
44
{
55
"file": "ErrorCodes.cs",
66
"_justification": "Constant contains the word Password as it is for the B2C password reset string value constant."
7-
}
7+
},
8+
{
9+
"file": "TestConstants.cs",
10+
"_justification": "Constant contains a certificate that is not used for anything other than running tests and has a private key."
11+
},
12+
{
13+
"file": "SelfSignedTestCert.pfx",
14+
"_justification": "Cert used for running tests."
15+
}
816
]
917
}
10-

src/Microsoft.Identity.Web.Certificate/Base64EncodedCertificateLoader.cs

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,43 @@ internal sealed class Base64EncodedCertificateLoader : ICredentialLoader
1313

1414
public void LoadIfNeeded(CredentialDescription credentialDescription)
1515
{
16-
credentialDescription.Certificate = LoadFromBase64Encoded(
17-
credentialDescription.Base64EncodedValue!,
18-
CertificateLoaderHelper.DetermineX509KeyStorageFlag(credentialDescription));
16+
_ = credentialDescription ?? throw new ArgumentNullException(nameof(credentialDescription));
17+
18+
if (credentialDescription.Certificate != null)
19+
return;
20+
21+
if (string.IsNullOrEmpty(credentialDescription.CertificatePassword))
22+
{
23+
credentialDescription.Certificate = LoadFromBase64Encoded(
24+
credentialDescription.Base64EncodedValue!,
25+
CertificateLoaderHelper.DetermineX509KeyStorageFlag(credentialDescription));
26+
}
27+
else
28+
{
29+
credentialDescription.Certificate = LoadFromBase64Encoded(
30+
credentialDescription.Base64EncodedValue!,
31+
credentialDescription.CertificatePassword!,
32+
CertificateLoaderHelper.DetermineX509KeyStorageFlag(credentialDescription));
33+
}
34+
1935
credentialDescription.CachedValue = credentialDescription.Certificate;
36+
2037
}
2138

2239
internal static X509Certificate2 LoadFromBase64Encoded(string certificateBase64, X509KeyStorageFlags x509KeyStorageFlags)
2340
{
24-
byte[] decoded = Convert.FromBase64String(certificateBase64);
2541
return new X509Certificate2(
26-
decoded,
42+
Convert.FromBase64String(certificateBase64),
2743
(string?)null,
2844
x509KeyStorageFlags);
2945
}
46+
47+
internal static X509Certificate2 LoadFromBase64Encoded(string certificateBase64, string password, X509KeyStorageFlags x509KeyStorageFlags)
48+
{
49+
return new X509Certificate2(
50+
Convert.FromBase64String(certificateBase64),
51+
password,
52+
x509KeyStorageFlags);
53+
}
3054
}
3155
}

src/Microsoft.Identity.Web.Certificate/CertificateDescription.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,22 @@ public static CertificateDescription FromBase64Encoded(string base64EncodedValue
8282
};
8383
}
8484

85+
/// <summary>
86+
/// Creates a certificate description from a Base64 encoded value.
87+
/// </summary>
88+
/// <param name="base64EncodedValue">Base64 encoded certificate value.</param>
89+
/// <param name="password">The password to use when decoding the certificate.</param>
90+
/// <returns>A certificate description.</returns>
91+
public static CertificateDescription FromBase64Encoded(string base64EncodedValue, string password)
92+
{
93+
return new CertificateDescription
94+
{
95+
SourceType = CertificateSource.Base64Encoded,
96+
Base64EncodedValue = base64EncodedValue,
97+
CertificatePassword = password
98+
};
99+
}
100+
85101
/// <summary>
86102
/// Creates a certificate description from path on disk.
87103
/// </summary>

src/Microsoft.Identity.Web.Certificate/FromPathCertificateLoader.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ internal sealed class FromPathCertificateLoader : ICredentialLoader
1313
public void LoadIfNeeded(CredentialDescription credentialDescription)
1414
{
1515
credentialDescription.Certificate = LoadFromPath(
16-
credentialDescription.CertificateStorePath!,
16+
credentialDescription.CertificateDiskPath!,
1717
credentialDescription.CertificatePassword!);
1818
credentialDescription.CachedValue = credentialDescription.Certificate;
1919
}

tests/Microsoft.Identity.Web.Test.Common/TestConstants.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ public static class TestConstants
121121
y1NFZmB24rMoq8C+HPOpuVLzkwBr+qcCq7ry2326auogvVMGaxhHlwSLR4Q1OhRjKs8JctCk2+5Qs1NHfawa7jWHxdAK6cLm7Rv/c0ig2Jow7wRaI5ciAcEjX7
122122
m1t9gRT1mNeeluL4cZa6WyVXqXc6U2wfR5DY6GOMUubN5Nr1n8Czew8TPfab4OG37BuEMNmBpqoRrRgFnDzVtItOnhuFTa0=";
123123

124+
// This value is only for testing purposes. It is for a certificate that is not used for anything other than running tests and has a private key.
125+
public const string CertificateX5cWithPrivateKey = @"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";
126+
public const string CertificateX5cWithPrivateKeyPassword = "SelfSignedTestCert";
127+
124128
public static string DecryptTokenCertificateDescriptionJson = "{" +
125129
"\"SourceType\": \"Base64Encoded\"," +
126130
$"\"Base64EncodedValue\": \"{CertificateX5c}\"," +

tests/Microsoft.Identity.Web.Test/Certificates/DefaultCertificateLoaderTests.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,32 @@ public void TestLoadAllCertificates(
102102
Assert.Null(certDescriptions.ElementAt(2).Certificate);
103103
}
104104

105+
[InlineData(CertificateSource.Base64Encoded, TestConstants.CertificateX5cWithPrivateKey, TestConstants.CertificateX5cWithPrivateKeyPassword)]
106+
[InlineData(CertificateSource.Path, "Certificates\\SelfSignedTestCert.pfx", TestConstants.CertificateX5cWithPrivateKeyPassword)]
107+
[Theory]
108+
public void TestLoadCertificateWithPrivateKey(
109+
CertificateSource certificateSource,
110+
string container,
111+
string password)
112+
{
113+
CertificateDescription certificateDescription;
114+
115+
if (certificateSource == CertificateSource.Base64Encoded)
116+
{
117+
certificateDescription = CertificateDescription.FromBase64Encoded(container, password);
118+
}
119+
else
120+
{
121+
certificateDescription = CertificateDescription.FromPath(container, password);
122+
}
123+
124+
DefaultCertificateLoader defaultCertificateLoader = new DefaultCertificateLoader();
125+
defaultCertificateLoader.LoadIfNeeded(certificateDescription);
126+
127+
Assert.NotNull(certificateDescription.Certificate);
128+
Assert.True(certificateDescription.Certificate.HasPrivateKey);
129+
}
130+
105131
private IEnumerable<CertificateDescription> CreateCertificateDescriptions(
106132
CertificateSource certificateSource,
107133
string container,
Binary file not shown.

tests/Microsoft.Identity.Web.Test/Microsoft.Identity.Web.Test.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,10 @@
5959
<Compile Include="CacheEncryptionTests.cs" />
6060
</ItemGroup>
6161

62+
<ItemGroup>
63+
<None Update="Certificates\SelfSignedTestCert.pfx">
64+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
65+
</None>
66+
</ItemGroup>
67+
6268
</Project>

0 commit comments

Comments
 (0)