-
Notifications
You must be signed in to change notification settings - Fork 55
/
Copy pathEd25519.cs
238 lines (210 loc) · 9.74 KB
/
Ed25519.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
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading;
using NSec.Cryptography.Formatting;
using static Interop.Libsodium;
namespace NSec.Cryptography
{
//
// Ed25519
//
// Digital Signature Algorithm based on the edwards25519 curve in
// pure mode (PureEdDSA)
//
// References:
//
// RFC 8032 - Edwards-Curve Digital Signature Algorithm (EdDSA)
//
// RFC 5958 - Asymmetric Key Packages
//
// RFC 8410 - Algorithm Identifiers for Ed25519, Ed448, X25519, and
// X448 for Use in the Internet X.509 Public Key Infrastructure
//
// Parameters:
//
// Private Key Size - The private key is 32 bytes (256 bits). However,
// the libsodium representation of a private key is 64 bytes. We
// expose private keys as 32-byte byte strings and internally
// convert from/to the libsodium format as necessary.
//
// Public Key Size - 32 bytes.
//
// Signature Size - 64 bytes.
//
public sealed class Ed25519 : SignatureAlgorithm
{
private static readonly PrivateKeyFormatter s_nsecPrivateKeyFormatter = new Ed25519PrivateKeyFormatter(new byte[] { 0xDE, 0x64, 0x42, 0xDE, crypto_sign_ed25519_SEEDBYTES, 0, crypto_sign_ed25519_BYTES, 0 });
private static readonly PublicKeyFormatter s_nsecPublicKeyFormatter = new Ed25519PublicKeyFormatter(new byte[] { 0xDE, 0x65, 0x42, 0xDE, crypto_sign_ed25519_PUBLICKEYBYTES, 0, crypto_sign_ed25519_BYTES, 0 });
private static readonly PrivateKeyFormatter s_pkixPrivateKeyFormatter = new Ed25519PrivateKeyFormatter(new byte[]
{
// +-- SEQUENCE (3 elements)
// +-- INTEGER 0
// +-- SEQUENCE (1 element)
// | +-- OBJECT IDENTIFIER 1.3.101.112
// +-- OCTET STRING (1 element)
// +-- OCTET STRING (32 bytes)
0x30, 0x2E, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06,
0x03, 0x2B, 0x65, 0x70, 0x04, 0x22, 0x04, 0x20,
});
private static readonly PublicKeyFormatter s_pkixPublicKeyFormatter = new Ed25519PublicKeyFormatter(new byte[]
{
// +-- SEQUENCE (2 elements)
// +-- SEQUENCE (1 element)
// | +-- OBJECT IDENTIFIER 1.3.101.112
// +-- BIT STRING (256 bits)
0x30, 0x2A, 0x30, 0x05, 0x06, 0x03, 0x2B, 0x65,
0x70, 0x03, 0x21, 0x00,
});
private static readonly PrivateKeyFormatter s_rawPrivateKeyFormatter = new Ed25519PrivateKeyFormatter(Array.Empty<byte>());
private static readonly PublicKeyFormatter s_rawPublicKeyFormatter = new Ed25519PublicKeyFormatter(Array.Empty<byte>());
private static int s_selfTest;
public Ed25519() : base(
privateKeySize: crypto_sign_ed25519_SEEDBYTES,
publicKeySize: crypto_sign_ed25519_PUBLICKEYBYTES,
signatureSize: crypto_sign_ed25519_BYTES)
{
if (s_selfTest == 0)
{
SelfTest();
Interlocked.Exchange(ref s_selfTest, 1);
}
}
internal override unsafe void CreateKey(
ReadOnlySpan<byte> seed,
out SecureMemoryHandle keyHandle,
out PublicKey? publicKey)
{
if (Unsafe.SizeOf<PublicKeyBytes>() != crypto_sign_ed25519_PUBLICKEYBYTES)
{
throw Error.InvalidOperation_InternalError();
}
Debug.Assert(seed.Length == crypto_sign_ed25519_SEEDBYTES);
publicKey = new PublicKey(this);
keyHandle = SecureMemoryHandle.Create(crypto_sign_ed25519_SECRETKEYBYTES);
fixed (PublicKeyBytes* pk = publicKey)
fixed (byte* seed_ = seed)
{
int error = crypto_sign_ed25519_seed_keypair(pk, keyHandle, seed_);
Debug.Assert(error == 0);
}
}
internal override int GetSeedSize()
{
return crypto_sign_ed25519_SEEDBYTES;
}
private protected unsafe override void SignCore(
SecureMemoryHandle keyHandle,
ReadOnlySpan<byte> data,
Span<byte> signature)
{
Debug.Assert(keyHandle.Size == crypto_sign_ed25519_SECRETKEYBYTES);
Debug.Assert(signature.Length == crypto_sign_ed25519_BYTES);
fixed (byte* sig = signature)
fixed (byte* m = data)
{
int error = crypto_sign_ed25519_detached(
sig,
out ulong signatureLength,
m,
(ulong)data.Length,
keyHandle);
Debug.Assert(error == 0);
Debug.Assert((ulong)signature.Length == signatureLength);
}
}
internal override bool TryExportKey(
SecureMemoryHandle keyHandle,
KeyBlobFormat format,
Span<byte> blob,
out int blobSize)
{
return format switch
{
KeyBlobFormat.RawPrivateKey => s_rawPrivateKeyFormatter.TryExport(keyHandle, blob, out blobSize),
KeyBlobFormat.NSecPrivateKey => s_nsecPrivateKeyFormatter.TryExport(keyHandle, blob, out blobSize),
KeyBlobFormat.PkixPrivateKey => s_pkixPrivateKeyFormatter.TryExport(keyHandle, blob, out blobSize),
KeyBlobFormat.PkixPrivateKeyText => s_pkixPrivateKeyFormatter.TryExportText(keyHandle, blob, out blobSize),
_ => throw Error.Argument_FormatNotSupported(nameof(format), format.ToString()),
};
}
internal override bool TryExportPublicKey(
PublicKey publicKey,
KeyBlobFormat format,
Span<byte> blob,
out int blobSize)
{
return format switch
{
KeyBlobFormat.RawPublicKey => s_rawPublicKeyFormatter.TryExport(in publicKey.GetPinnableReference(), blob, out blobSize),
KeyBlobFormat.NSecPublicKey => s_nsecPublicKeyFormatter.TryExport(in publicKey.GetPinnableReference(), blob, out blobSize),
KeyBlobFormat.PkixPublicKey => s_pkixPublicKeyFormatter.TryExport(in publicKey.GetPinnableReference(), blob, out blobSize),
KeyBlobFormat.PkixPublicKeyText => s_pkixPublicKeyFormatter.TryExportText(in publicKey.GetPinnableReference(), blob, out blobSize),
_ => throw Error.Argument_FormatNotSupported(nameof(format), format.ToString()),
};
}
internal override bool TryImportKey(
ReadOnlySpan<byte> blob,
KeyBlobFormat format,
out SecureMemoryHandle? keyHandle,
out PublicKey? publicKey)
{
publicKey = new PublicKey(this);
return format switch
{
KeyBlobFormat.RawPrivateKey => s_rawPrivateKeyFormatter.TryImport(blob, out keyHandle, out publicKey.GetPinnableReference()),
KeyBlobFormat.NSecPrivateKey => s_nsecPrivateKeyFormatter.TryImport(blob, out keyHandle, out publicKey.GetPinnableReference()),
KeyBlobFormat.PkixPrivateKey => s_pkixPrivateKeyFormatter.TryImport(blob, out keyHandle, out publicKey.GetPinnableReference()),
KeyBlobFormat.PkixPrivateKeyText => s_pkixPrivateKeyFormatter.TryImportText(blob, out keyHandle, out publicKey.GetPinnableReference()),
_ => throw Error.Argument_FormatNotSupported(nameof(format), format.ToString()),
};
}
internal override bool TryImportPublicKey(
ReadOnlySpan<byte> blob,
KeyBlobFormat format,
out PublicKey publicKey)
{
publicKey = new PublicKey(this);
return format switch
{
KeyBlobFormat.RawPublicKey => s_rawPublicKeyFormatter.TryImport(blob, out publicKey.GetPinnableReference()),
KeyBlobFormat.NSecPublicKey => s_nsecPublicKeyFormatter.TryImport(blob, out publicKey.GetPinnableReference()),
KeyBlobFormat.PkixPublicKey => s_pkixPublicKeyFormatter.TryImport(blob, out publicKey.GetPinnableReference()),
KeyBlobFormat.PkixPublicKeyText => s_pkixPublicKeyFormatter.TryImportText(blob, out publicKey.GetPinnableReference()),
_ => throw Error.Argument_FormatNotSupported(nameof(format), format.ToString()),
};
}
private protected unsafe override bool VerifyCore(
in PublicKeyBytes publicKeyBytes,
ReadOnlySpan<byte> data,
ReadOnlySpan<byte> signature)
{
if (Unsafe.SizeOf<PublicKeyBytes>() != crypto_sign_ed25519_PUBLICKEYBYTES)
{
throw Error.InvalidOperation_InternalError();
}
Debug.Assert(signature.Length == crypto_sign_ed25519_BYTES);
fixed (byte* sig = signature)
fixed (byte* m = data)
fixed (PublicKeyBytes* pk = &publicKeyBytes)
{
int error = crypto_sign_ed25519_verify_detached(
sig,
m,
(ulong)data.Length,
pk);
return error == 0;
}
}
private static void SelfTest()
{
if ((crypto_sign_ed25519_bytes() != crypto_sign_ed25519_BYTES) ||
(crypto_sign_ed25519_publickeybytes() != crypto_sign_ed25519_PUBLICKEYBYTES) ||
(crypto_sign_ed25519_secretkeybytes() != crypto_sign_ed25519_SECRETKEYBYTES) ||
(crypto_sign_ed25519_seedbytes() != crypto_sign_ed25519_SEEDBYTES))
{
throw Error.InvalidOperation_InitializationFailed();
}
}
}
}