/* ======================================================================== * Copyright (c) 2005-2026 The OPC Foundation, Inc. All rights reserved. * * OPC Foundation MIT License 1.00 * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * The complete license agreement can be found here: * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; namespace Opc.Ua.Identity { /// /// Resolves JWT issuer verification keys from a fixed in-memory set. /// /// /// The resolver owns the supplied /// instances and disposes them when the resolver is disposed. /// Lookup by kid uses case-sensitive ordinal comparison. /// public sealed class StaticIssuerKeyResolver : IIssuerKeyResolver, IDisposable { private readonly IReadOnlyList m_keys; private readonly Dictionary> m_keysById; private bool m_disposed; /// /// Creates a fixed-key resolver for one trusted issuer. /// /// The trusted JWT iss claim. /// Verification keys owned by this resolver. /// /// is empty, or contains a null element. /// /// is . public StaticIssuerKeyResolver(string issuerUri, IEnumerable keys) { if (string.IsNullOrEmpty(issuerUri)) { throw new ArgumentException( "Issuer URI must be supplied.", nameof(issuerUri)); } if (keys == null) { throw new ArgumentNullException(nameof(keys)); } IssuerUri = issuerUri; var allKeys = new List(); var keyed = new Dictionary>(StringComparer.Ordinal); foreach (IssuerVerificationKey key in keys) { if (key == null) { throw new ArgumentException( "Verification key collection must not contain null elements.", nameof(keys)); } allKeys.Add(key); if (key.KeyId != null) { if (!keyed.TryGetValue(key.KeyId, out List? matchingKeys)) { matchingKeys = []; keyed[key.KeyId] = matchingKeys; } matchingKeys.Add(key); } } m_keys = allKeys.AsReadOnly(); var readOnlyById = new Dictionary>( StringComparer.Ordinal); foreach (KeyValuePair> item in keyed) { readOnlyById[item.Key] = item.Value.AsReadOnly(); } m_keysById = readOnlyById; } /// public string IssuerUri { get; } /// public ValueTask> GetKeysAsync( string? keyId, CancellationToken ct = default) { if (m_disposed) { throw new ObjectDisposedException(nameof(StaticIssuerKeyResolver)); } if (keyId == null) { return new ValueTask>(m_keys); } return new ValueTask>( m_keysById.TryGetValue(keyId, out IReadOnlyList? keys) ? keys : []); } /// public void Dispose() { if (m_disposed) { return; } m_disposed = true; foreach (IssuerVerificationKey key in m_keys) { key.Dispose(); } } } }