/
Interop.SecKeyRef.cs
213 lines (183 loc) · 7.23 KB
/
Interop.SecKeyRef.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Security.Cryptography.Apple;
using Microsoft.Win32.SafeHandles;
internal static partial class Interop
{
internal static partial class AppleCrypto
{
private const int kSuccess = 1;
private const int kErrorSeeError = -2;
private const int kPlatformNotSupported = -5;
internal enum PAL_KeyAlgorithm : uint
{
Unknown = 0,
EC = 1,
RSA = 2,
}
[LibraryImport(Libraries.AppleCryptoNative)]
private static partial ulong AppleCryptoNative_SecKeyGetSimpleKeySizeInBytes(SafeSecKeyRefHandle publicKey);
private delegate int SecKeyTransform(ReadOnlySpan<byte> source, out SafeCFDataHandle outputHandle, out SafeCFErrorHandle errorHandle);
private static byte[] ExecuteTransform(ReadOnlySpan<byte> source, SecKeyTransform transform)
{
SafeCFDataHandle data;
SafeCFErrorHandle error;
int ret = transform(source, out data, out error);
using (error)
using (data)
{
if (ret == kSuccess)
{
return CoreFoundation.CFGetData(data);
}
if (ret == kErrorSeeError)
{
throw CreateExceptionForCFError(error);
}
Debug.Fail($"transform returned {ret}");
throw new CryptographicException();
}
}
private static bool TryExecuteTransform(
ReadOnlySpan<byte> source,
Span<byte> destination,
out int bytesWritten,
SecKeyTransform transform)
{
SafeCFDataHandle outputHandle;
SafeCFErrorHandle errorHandle;
int ret = transform(source, out outputHandle, out errorHandle);
using (errorHandle)
using (outputHandle)
{
switch (ret)
{
case kSuccess:
return CoreFoundation.TryCFWriteData(outputHandle, destination, out bytesWritten);
case kErrorSeeError:
throw CreateExceptionForCFError(errorHandle);
default:
Debug.Fail($"transform returned {ret}");
throw new CryptographicException();
}
}
}
internal static int GetSimpleKeySizeInBits(SafeSecKeyRefHandle publicKey)
{
ulong keySizeInBytes = AppleCryptoNative_SecKeyGetSimpleKeySizeInBytes(publicKey);
checked
{
return (int)(keySizeInBytes * 8);
}
}
internal static unsafe SafeSecKeyRefHandle CreateDataKey(
ReadOnlySpan<byte> keyData,
PAL_KeyAlgorithm keyAlgorithm,
bool isPublic)
{
fixed (byte* pKey = keyData)
{
int result = AppleCryptoNative_SecKeyCreateWithData(
pKey,
keyData.Length,
keyAlgorithm,
isPublic ? 1 : 0,
out SafeSecKeyRefHandle dataKey,
out SafeCFErrorHandle errorHandle);
using (errorHandle)
{
switch (result)
{
case kSuccess:
return dataKey;
case kErrorSeeError:
throw CreateExceptionForCFError(errorHandle);
default:
Debug.Fail($"SecKeyCreateWithData returned {result}");
throw new CryptographicException();
}
}
}
}
internal static bool TrySecKeyCopyExternalRepresentation(
SafeSecKeyRefHandle key,
out byte[] externalRepresentation)
{
const int errSecPassphraseRequired = -25260;
// macOS Sonoma 14.4 started returning errSecInvalidKeyAttributeMask when a key could not be exported
// because it must be exported with a password.
const int errSecInvalidKeyAttributeMask = -67738;
int result = AppleCryptoNative_SecKeyCopyExternalRepresentation(
key,
out SafeCFDataHandle data,
out SafeCFErrorHandle errorHandle);
using (errorHandle)
using (data)
{
switch (result)
{
case kSuccess:
externalRepresentation = CoreFoundation.CFGetData(data);
return true;
case kErrorSeeError:
if (Interop.CoreFoundation.GetErrorCode(errorHandle) is errSecPassphraseRequired or errSecInvalidKeyAttributeMask)
{
externalRepresentation = Array.Empty<byte>();
return false;
}
throw CreateExceptionForCFError(errorHandle);
default:
Debug.Fail($"SecKeyCopyExternalRepresentation returned {result}");
throw new CryptographicException();
}
}
}
[LibraryImport(Libraries.AppleCryptoNative)]
private static unsafe partial int AppleCryptoNative_SecKeyCreateWithData(
byte* pKey,
int cbKey,
PAL_KeyAlgorithm keyAlgorithm,
int isPublic,
out SafeSecKeyRefHandle pDataKey,
out SafeCFErrorHandle pErrorOut);
[LibraryImport(Libraries.AppleCryptoNative)]
private static unsafe partial int AppleCryptoNative_SecKeyCopyExternalRepresentation(
SafeSecKeyRefHandle key,
out SafeCFDataHandle pDataOut,
out SafeCFErrorHandle pErrorOut);
[LibraryImport(Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_SecKeyCopyPublicKey")]
internal static unsafe partial SafeSecKeyRefHandle CopyPublicKey(SafeSecKeyRefHandle privateKey);
}
}
namespace System.Security.Cryptography.Apple
{
internal sealed class SafeSecKeyRefHandle : SafeHandle
{
public SafeSecKeyRefHandle()
: base(IntPtr.Zero, ownsHandle: true)
{
}
protected override bool ReleaseHandle()
{
Interop.CoreFoundation.CFRelease(handle);
SetHandle(IntPtr.Zero);
return true;
}
public override bool IsInvalid => handle == IntPtr.Zero;
protected override void Dispose(bool disposing)
{
if (disposing && SafeHandleCache<SafeSecKeyRefHandle>.IsCachedInvalidHandle(this))
{
return;
}
base.Dispose(disposing);
}
public static SafeSecKeyRefHandle InvalidHandle =>
SafeHandleCache<SafeSecKeyRefHandle>.GetInvalidHandle(
() => new SafeSecKeyRefHandle());
}
}