#region Copyright notice and license // Copyright 2019 The gRPC Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #endregion using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Runtime.CompilerServices; using Grpc.Core.Utils; using Grpc.Core.Logging; namespace Grpc.Core.Internal { /// /// Creates native call credential objects from instances of ChannelCredentials. /// internal class DefaultChannelCredentialsConfigurator : ChannelCredentialsConfiguratorBase { static readonly ILogger Logger = GrpcEnvironment.Logger.ForType(); // Native credentials object need to be kept alive once initialized for subchannel sharing to work correctly // with secure connections. See https://github.com/grpc/grpc/issues/15207. // We rely on finalizer to clean up the native portion of ChannelCredentialsSafeHandle after the ChannelCredentials // instance becomes unused. static readonly ConditionalWeakTable> CachedNativeCredentials = new ConditionalWeakTable>(); static readonly object StaticLock = new object(); bool configured; ChannelCredentialsSafeHandle nativeCredentials; public ChannelCredentialsSafeHandle NativeCredentials => nativeCredentials; public override void SetInsecureCredentials(object state) { GrpcPreconditions.CheckState(!configured); // null corresponds to insecure credentials. configured = true; nativeCredentials = null; } public override void SetSslCredentials(object state, string rootCertificates, KeyCertificatePair keyCertificatePair, VerifyPeerCallback verifyPeerCallback) { GrpcPreconditions.CheckState(!configured); configured = true; nativeCredentials = GetOrCreateNativeCredentials((ChannelCredentials) state, () => CreateNativeSslCredentials(rootCertificates, keyCertificatePair, verifyPeerCallback)); } public override void SetCompositeCredentials(object state, ChannelCredentials channelCredentials, CallCredentials callCredentials) { GrpcPreconditions.CheckState(!configured); configured = true; nativeCredentials = GetOrCreateNativeCredentials((ChannelCredentials) state, () => CreateNativeCompositeCredentials(channelCredentials, callCredentials)); } private ChannelCredentialsSafeHandle CreateNativeSslCredentials(string rootCertificates, KeyCertificatePair keyCertificatePair, VerifyPeerCallback verifyPeerCallback) { IntPtr verifyPeerCallbackTag = IntPtr.Zero; if (verifyPeerCallback != null) { verifyPeerCallbackTag = new VerifyPeerCallbackRegistration(verifyPeerCallback).CallbackRegistration.Tag; } return ChannelCredentialsSafeHandle.CreateSslCredentials(rootCertificates, keyCertificatePair, verifyPeerCallbackTag); } private ChannelCredentialsSafeHandle CreateNativeCompositeCredentials(ChannelCredentials channelCredentials, CallCredentials callCredentials) { using (var callCreds = callCredentials.ToNativeCredentials()) { var nativeComposite = ChannelCredentialsSafeHandle.CreateComposite(channelCredentials.ToNativeCredentials(), callCreds); if (nativeComposite.IsInvalid) { throw new ArgumentException("Error creating native composite credentials. Likely, this is because you are trying to compose incompatible credentials."); } return nativeComposite; } } private ChannelCredentialsSafeHandle GetOrCreateNativeCredentials(ChannelCredentials key, Func nativeCredentialsFactory) { Lazy lazyValue; lock (StaticLock) { if (!CachedNativeCredentials.TryGetValue(key, out lazyValue)) { lazyValue = new Lazy(nativeCredentialsFactory); CachedNativeCredentials.Add(key, lazyValue); } } return lazyValue.Value; } private class VerifyPeerCallbackRegistration { readonly VerifyPeerCallback verifyPeerCallback; readonly NativeCallbackRegistration callbackRegistration; public VerifyPeerCallbackRegistration(VerifyPeerCallback verifyPeerCallback) { this.verifyPeerCallback = verifyPeerCallback; this.callbackRegistration = NativeCallbackDispatcher.RegisterCallback(HandleUniversalCallback); } public NativeCallbackRegistration CallbackRegistration => callbackRegistration; private int HandleUniversalCallback(IntPtr arg0, IntPtr arg1, IntPtr arg2, IntPtr arg3, IntPtr arg4, IntPtr arg5) { return VerifyPeerCallbackHandler(arg0, arg1, arg2 != IntPtr.Zero); } private int VerifyPeerCallbackHandler(IntPtr targetName, IntPtr peerPem, bool isDestroy) { if (isDestroy) { this.callbackRegistration.Dispose(); return 0; } try { var context = new VerifyPeerContext(Marshal.PtrToStringAnsi(targetName), Marshal.PtrToStringAnsi(peerPem)); return this.verifyPeerCallback(context) ? 0 : 1; } catch (Exception e) { // eat the exception, we must not throw when inside callback from native code. Logger.Error(e, "Exception occurred while invoking verify peer callback handler."); // Return validation failure in case of exception. return 1; } } } } internal static class ChannelCredentialsExtensions { /// /// Creates native object for the credentials. /// /// The native credentials. public static ChannelCredentialsSafeHandle ToNativeCredentials(this ChannelCredentials credentials) { var configurator = new DefaultChannelCredentialsConfigurator(); credentials.InternalPopulateConfiguration(configurator, credentials); return configurator.NativeCredentials; } } }