Browse Source

Fix the user code generation logic

pull/1580/head
Kévin Chalet 3 years ago
parent
commit
06afece93a
  1. 1
      Directory.Build.targets
  2. 28
      sandbox/OpenIddict.Sandbox.AspNet.Server/Startup.cs
  3. 2
      sandbox/OpenIddict.Sandbox.AspNetCore.Server/Worker.cs
  4. 84
      shared/OpenIddict.Extensions/Helpers/OpenIddictHelpers.cs
  5. 23
      src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs

1
Directory.Build.targets

@ -63,6 +63,7 @@
<PropertyGroup
Condition=" ('$(TargetFrameworkIdentifier)' == '.NETCoreApp' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '3.0'))) ">
<DefineConstants>$(DefineConstants);SUPPORTS_HTTP_CLIENT_DEFAULT_REQUEST_VERSION</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_INTEGER32_RANDOM_NUMBER_GENERATOR_METHODS</DefineConstants>
</PropertyGroup>
<PropertyGroup

28
sandbox/OpenIddict.Sandbox.AspNet.Server/Startup.cs

@ -97,6 +97,34 @@ namespace OpenIddict.Sandbox.AspNet.Server
}
});
}
if (await manager.FindByClientIdAsync("postman") is null)
{
await manager.CreateAsync(new OpenIddictApplicationDescriptor
{
ClientId = "postman",
ConsentType = ConsentTypes.Systematic,
DisplayName = "Postman",
RedirectUris =
{
new Uri("https://oauth.pstmn.io/v1/callback")
},
Permissions =
{
Permissions.Endpoints.Authorization,
Permissions.Endpoints.Device,
Permissions.Endpoints.Token,
Permissions.GrantTypes.AuthorizationCode,
Permissions.GrantTypes.DeviceCode,
Permissions.GrantTypes.Password,
Permissions.GrantTypes.RefreshToken,
Permissions.ResponseTypes.Code,
Permissions.Scopes.Email,
Permissions.Scopes.Profile,
Permissions.Scopes.Roles
}
});
}
}).GetAwaiter().GetResult();
}

2
sandbox/OpenIddict.Sandbox.AspNetCore.Server/Worker.cs

@ -100,7 +100,7 @@ public class Worker : IHostedService
DisplayName = "Postman",
RedirectUris =
{
new Uri("urn:postman")
new Uri("https://oauth.pstmn.io/v1/callback")
},
Permissions =
{

84
shared/OpenIddict.Extensions/Helpers/OpenIddictHelpers.cs

@ -1,6 +1,5 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
@ -538,6 +537,89 @@ internal static class OpenIddictHelpers
return array;
}
/// <summary>
/// Creates a new <see cref="string"/> containing characters
/// randomly selected in the specified <paramref name="charset"/>.
/// </summary>
/// <param name="charset">The characters allowed to be included in the <see cref="string"/>.</param>
/// <param name="length">The desired length of the <see cref="string"/>.</param>
/// <returns>A new <see cref="string"/> containing random data.</returns>
/// <exception cref="CryptographicException">
/// The implementation resolved from <see cref="CryptoConfig.CreateFromName(string)"/> is not valid.
/// </exception>
public static string CreateRandomString(ReadOnlySpan<char> charset, int length)
{
var algorithm = CryptoConfig.CreateFromName("OpenIddict RNG Cryptographic Provider") switch
{
RandomNumberGenerator result => result,
null => null,
var result => throw new CryptographicException(SR.FormatID0351(result.GetType().FullName))
};
try
{
var buffer = new char[length];
for (var index = 0; index < buffer.Length; index++)
{
// Pick a character in the specified charset by generating a random index.
buffer[index] = charset[index: algorithm switch
{
#if SUPPORTS_INTEGER32_RANDOM_NUMBER_GENERATOR_METHODS
// If no custom random number generator was registered, use
// the static GetInt32() API on platforms that support it.
null => RandomNumberGenerator.GetInt32(0, charset.Length),
#endif
// Otherwise, create a default implementation if necessary
// and use the local function that achieves the same result.
_ => GetInt32(algorithm ??= RandomNumberGenerator.Create(), 0..charset.Length)
}];
}
return new string(buffer);
}
finally
{
algorithm?.Dispose();
}
static int GetInt32(RandomNumberGenerator algorithm, Range range)
{
// Note: the logic used here is directly taken from the official implementation
// of the RandomNumberGenerator.GetInt32() method introduced in .NET Core 3.0.
//
// See https://github.com/dotnet/corefx/pull/31243 for more information.
var count = (uint) range.End.Value - (uint) range.Start.Value - 1;
if (count is 0)
{
return range.Start.Value;
}
var mask = count;
mask |= mask >> 1;
mask |= mask >> 2;
mask |= mask >> 4;
mask |= mask >> 8;
mask |= mask >> 16;
var buffer = new byte[sizeof(uint)];
uint value;
do
{
algorithm.GetBytes(buffer);
value = mask & BitConverter.ToUInt32(buffer, 0);
}
while (value > count);
return (int) value + range.Start.Value;
}
}
#if SUPPORTS_KEY_DERIVATION_WITH_SPECIFIED_HASH_ALGORITHM
/// <summary>
/// Creates a derived key based on the specified <paramref name="secret"/> using PBKDF2.

23
src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs

@ -1318,24 +1318,21 @@ public static partial class OpenIddictServerHandlers
descriptor.Payload = context.Token;
descriptor.Principal = context.Principal;
// Note: unlike other reference tokens, user codes are meant to be used by humans,
// who may have to enter it in a web form. To ensure it remains easy enough to type
// even by users with non-Latin keyboards, user codes generated by OpenIddict are
// only compound of 12 digits, generated using a crypto-secure random number generator.
// In this case, the resulting user code is estimated to have at most ~40 bits of entropy.
if (context.TokenType is TokenTypeHints.UserCode)
{
do
{
var array = OpenIddictHelpers.CreateRandomArray(size: 12);
var builder = new StringBuilder(array.Length);
// Note: unlike other reference tokens, user codes are meant to be used by humans,
// who may have to enter it in a web form. To ensure it remains easy enough to type
// even by users with non-Latin keyboards, user codes generated by OpenIddict are
// only compound of 12 digits, generated using a crypto-secure random number generator.
// In this case, the resulting user code is estimated to have at most ~40 bits of entropy.
for (var index = 0; index < array.Length; index += 4)
{
builder.AppendFormat(CultureInfo.InvariantCulture, "{0:D4}", BitConverter.ToUInt32(array, index) % 10000);
}
static string CreateRandomNumericCode(int length) => OpenIddictHelpers.CreateRandomString(
charset: stackalloc[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' },
length: length);
descriptor.ReferenceId = builder.ToString();
descriptor.ReferenceId = CreateRandomNumericCode(length: 12);
}
// User codes are relatively short. To help reduce the risks of collisions with
@ -1343,9 +1340,9 @@ public static partial class OpenIddictServerHandlers
while (await _tokenManager.FindByReferenceIdAsync(descriptor.ReferenceId) is not null);
}
// For other tokens, generate a base64url-encoded 256-bit random identifier.
else
{
// For other tokens, generate a base64url-encoded 256-bit random identifier.
descriptor.ReferenceId = Base64UrlEncoder.Encode(OpenIddictHelpers.CreateRandomArray(size: 256));
}

Loading…
Cancel
Save