1 #region Copyright notice and license 2 3 // Copyright 2019 The gRPC Authors 4 // 5 // Licensed under the Apache License, Version 2.0 (the "License"); 6 // you may not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, software 12 // distributed under the License is distributed on an "AS IS" BASIS, 13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 // See the License for the specific language governing permissions and 15 // limitations under the License. 16 17 #endregion 18 19 using System; 20 using System.Collections.Generic; 21 using System.Runtime.InteropServices; 22 using System.Runtime.CompilerServices; 23 using Grpc.Core.Utils; 24 using Grpc.Core.Logging; 25 26 namespace Grpc.Core.Internal 27 { 28 /// <summary> 29 /// Creates native call credential objects from instances of <c>ChannelCredentials</c>. 30 /// </summary> 31 internal class DefaultChannelCredentialsConfigurator : ChannelCredentialsConfiguratorBase 32 { 33 static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<DefaultCallCredentialsConfigurator>(); 34 35 // Native credentials object need to be kept alive once initialized for subchannel sharing to work correctly 36 // with secure connections. See https://github.com/grpc/grpc/issues/15207. 37 // We rely on finalizer to clean up the native portion of ChannelCredentialsSafeHandle after the ChannelCredentials 38 // instance becomes unused. 39 static readonly ConditionalWeakTable<ChannelCredentials, Lazy<ChannelCredentialsSafeHandle>> CachedNativeCredentials = new ConditionalWeakTable<ChannelCredentials, Lazy<ChannelCredentialsSafeHandle>>(); 40 static readonly object StaticLock = new object(); 41 42 bool configured; 43 ChannelCredentialsSafeHandle nativeCredentials; 44 45 public ChannelCredentialsSafeHandle NativeCredentials => nativeCredentials; 46 SetInsecureCredentials(object state)47 public override void SetInsecureCredentials(object state) 48 { 49 GrpcPreconditions.CheckState(!configured); 50 // null corresponds to insecure credentials. 51 configured = true; 52 nativeCredentials = null; 53 } 54 SetSslCredentials(object state, string rootCertificates, KeyCertificatePair keyCertificatePair, VerifyPeerCallback verifyPeerCallback)55 public override void SetSslCredentials(object state, string rootCertificates, KeyCertificatePair keyCertificatePair, VerifyPeerCallback verifyPeerCallback) 56 { 57 GrpcPreconditions.CheckState(!configured); 58 configured = true; 59 nativeCredentials = GetOrCreateNativeCredentials((ChannelCredentials) state, 60 () => CreateNativeSslCredentials(rootCertificates, keyCertificatePair, verifyPeerCallback)); 61 } 62 SetCompositeCredentials(object state, ChannelCredentials channelCredentials, CallCredentials callCredentials)63 public override void SetCompositeCredentials(object state, ChannelCredentials channelCredentials, CallCredentials callCredentials) 64 { 65 GrpcPreconditions.CheckState(!configured); 66 configured = true; 67 nativeCredentials = GetOrCreateNativeCredentials((ChannelCredentials) state, 68 () => CreateNativeCompositeCredentials(channelCredentials, callCredentials)); 69 } 70 CreateNativeSslCredentials(string rootCertificates, KeyCertificatePair keyCertificatePair, VerifyPeerCallback verifyPeerCallback)71 private ChannelCredentialsSafeHandle CreateNativeSslCredentials(string rootCertificates, KeyCertificatePair keyCertificatePair, VerifyPeerCallback verifyPeerCallback) 72 { 73 IntPtr verifyPeerCallbackTag = IntPtr.Zero; 74 if (verifyPeerCallback != null) 75 { 76 verifyPeerCallbackTag = new VerifyPeerCallbackRegistration(verifyPeerCallback).CallbackRegistration.Tag; 77 } 78 return ChannelCredentialsSafeHandle.CreateSslCredentials(rootCertificates, keyCertificatePair, verifyPeerCallbackTag); 79 } 80 CreateNativeCompositeCredentials(ChannelCredentials channelCredentials, CallCredentials callCredentials)81 private ChannelCredentialsSafeHandle CreateNativeCompositeCredentials(ChannelCredentials channelCredentials, CallCredentials callCredentials) 82 { 83 using (var callCreds = callCredentials.ToNativeCredentials()) 84 { 85 var nativeComposite = ChannelCredentialsSafeHandle.CreateComposite(channelCredentials.ToNativeCredentials(), callCreds); 86 if (nativeComposite.IsInvalid) 87 { 88 throw new ArgumentException("Error creating native composite credentials. Likely, this is because you are trying to compose incompatible credentials."); 89 } 90 return nativeComposite; 91 } 92 } 93 GetOrCreateNativeCredentials(ChannelCredentials key, Func<ChannelCredentialsSafeHandle> nativeCredentialsFactory)94 private ChannelCredentialsSafeHandle GetOrCreateNativeCredentials(ChannelCredentials key, Func<ChannelCredentialsSafeHandle> nativeCredentialsFactory) 95 { 96 Lazy<ChannelCredentialsSafeHandle> lazyValue; 97 lock (StaticLock) { 98 if (!CachedNativeCredentials.TryGetValue(key, out lazyValue)) 99 { 100 lazyValue = new Lazy<ChannelCredentialsSafeHandle>(nativeCredentialsFactory); 101 CachedNativeCredentials.Add(key, lazyValue); 102 } 103 } 104 return lazyValue.Value; 105 } 106 107 private class VerifyPeerCallbackRegistration 108 { 109 readonly VerifyPeerCallback verifyPeerCallback; 110 readonly NativeCallbackRegistration callbackRegistration; 111 VerifyPeerCallbackRegistration(VerifyPeerCallback verifyPeerCallback)112 public VerifyPeerCallbackRegistration(VerifyPeerCallback verifyPeerCallback) 113 { 114 this.verifyPeerCallback = verifyPeerCallback; 115 this.callbackRegistration = NativeCallbackDispatcher.RegisterCallback(HandleUniversalCallback); 116 } 117 118 public NativeCallbackRegistration CallbackRegistration => callbackRegistration; 119 HandleUniversalCallback(IntPtr arg0, IntPtr arg1, IntPtr arg2, IntPtr arg3, IntPtr arg4, IntPtr arg5)120 private int HandleUniversalCallback(IntPtr arg0, IntPtr arg1, IntPtr arg2, IntPtr arg3, IntPtr arg4, IntPtr arg5) 121 { 122 return VerifyPeerCallbackHandler(arg0, arg1, arg2 != IntPtr.Zero); 123 } 124 VerifyPeerCallbackHandler(IntPtr targetName, IntPtr peerPem, bool isDestroy)125 private int VerifyPeerCallbackHandler(IntPtr targetName, IntPtr peerPem, bool isDestroy) 126 { 127 if (isDestroy) 128 { 129 this.callbackRegistration.Dispose(); 130 return 0; 131 } 132 133 try 134 { 135 var context = new VerifyPeerContext(Marshal.PtrToStringAnsi(targetName), Marshal.PtrToStringAnsi(peerPem)); 136 137 return this.verifyPeerCallback(context) ? 0 : 1; 138 } 139 catch (Exception e) 140 { 141 // eat the exception, we must not throw when inside callback from native code. 142 Logger.Error(e, "Exception occurred while invoking verify peer callback handler."); 143 // Return validation failure in case of exception. 144 return 1; 145 } 146 } 147 } 148 } 149 150 internal static class ChannelCredentialsExtensions 151 { 152 /// <summary> 153 /// Creates native object for the credentials. 154 /// </summary> 155 /// <returns>The native credentials.</returns> ToNativeCredentials(this ChannelCredentials credentials)156 public static ChannelCredentialsSafeHandle ToNativeCredentials(this ChannelCredentials credentials) 157 { 158 var configurator = new DefaultChannelCredentialsConfigurator(); 159 credentials.InternalPopulateConfiguration(configurator, credentials); 160 return configurator.NativeCredentials; 161 } 162 } 163 } 164