| File: Circuits\CircuitIdFactory.cs | Web Access |
| Project: src\aspnetcore\src\Components\Server\src\Microsoft.AspNetCore.Components.Server.csproj (Microsoft.AspNetCore.Components.Server) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Security.Cryptography; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.WebUtilities; namespace Microsoft.AspNetCore.Components.Server.Circuits; // This is a singleton instance // Generates strong cryptographic ids for circuits that are protected with authenticated encryption. internal sealed class CircuitIdFactory { private const string CircuitIdProtectorPurpose = "Microsoft.AspNetCore.Components.Server.CircuitIdFactory,V1"; // We use 64 bytes, where the last 32 are the public version of the id. // This way we can always recover the public id from the secret form. private const int SecretLength = 64; private const int IdLength = 32; private readonly IDataProtector _protector; public CircuitIdFactory(IDataProtectionProvider provider) { _protector = provider.CreateProtector(CircuitIdProtectorPurpose); } // Generates a circuit id that is produced from a strong cryptographic random number generator // we don't care about the underlying payload, other than its uniqueness and the fact that we // authenticate encrypt it using data protection. // For validation, the fact that we can unprotect the payload is guarantee enough. public CircuitId CreateCircuitId() { var buffer = new byte[SecretLength]; RandomNumberGenerator.Fill(buffer); var id = new byte[IdLength]; Array.Copy( sourceArray: buffer, sourceIndex: SecretLength - IdLength, destinationArray: id, destinationIndex: 0, length: IdLength); var secret = _protector.Protect(buffer); return new CircuitId(Base64UrlTextEncoder.Encode(secret), Base64UrlTextEncoder.Encode(id)); } public bool TryParseCircuitId(string? text, out CircuitId circuitId) { if (text is null) { circuitId = default; return false; } try { var protectedBytes = Base64UrlTextEncoder.Decode(text); var unprotectedBytes = _protector.Unprotect(protectedBytes); if (unprotectedBytes.Length != SecretLength) { // Wrong length circuitId = default; return false; } var id = new byte[IdLength]; Array.Copy( sourceArray: unprotectedBytes, sourceIndex: SecretLength - IdLength, destinationArray: id, destinationIndex: 0, length: IdLength); circuitId = new CircuitId(text, Base64UrlTextEncoder.Encode(id)); return true; } catch (Exception) { // The payload format is not correct (either not base64urlencoded or not data protected) circuitId = default; return false; } } }