-
Notifications
You must be signed in to change notification settings - Fork 12
/
openssl.go
469 lines (424 loc) · 13.6 KB
/
openssl.go
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
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
//go:build !cmd_go_bootstrap
// Package openssl provides access to OpenSSL cryptographic functions.
package openssl
// #include "goopenssl.h"
import "C"
import (
"encoding/binary"
"errors"
"math/bits"
"runtime"
"strconv"
"strings"
"sync"
"unsafe"
)
var (
// vMajor and vMinor hold the major/minor OpenSSL version.
// It is only populated if Init has been called.
vMajor, vMinor, vPatch uint
)
var (
initOnce sync.Once
initErr error
)
var nativeEndian binary.ByteOrder
// CheckVersion checks if the OpenSSL version can be loaded
// and if the FIPS mode is enabled.
// This function can be called before Init.
func CheckVersion(version string) (exists, fips bool) {
handle, _ := dlopen(version)
if handle == nil {
return false, false
}
defer dlclose(handle)
enabled := C.go_openssl_fips_enabled(handle)
fips = enabled == 1
// If go_openssl_fips_enabled returns -1, it means that all or some of the necessary
// functions are not available. This can be due to the version of OpenSSL being too old,
// too incompatible, or the shared library not being an OpenSSL library. In any case,
// we shouldn't consider this library to be valid for our purposes.
exists = enabled != -1
return
}
// Init loads and initializes OpenSSL from the shared library at path.
// It must be called before any other OpenSSL call, except CheckVersion.
//
// Only the first call to Init is effective.
// Subsequent calls will return the same error result as the one from the first call.
//
// The file is passed to dlopen() verbatim to load the OpenSSL shared library.
// For example, `file=libcrypto.so.1.1.1k-fips` makes Init look for the shared
// library libcrypto.so.1.1.1k-fips.
func Init(file string) error {
initOnce.Do(func() {
buf := [2]byte{}
*(*uint16)(unsafe.Pointer(&buf[0])) = uint16(0xABCD)
switch buf {
case [2]byte{0xCD, 0xAB}:
nativeEndian = binary.LittleEndian
case [2]byte{0xAB, 0xCD}:
nativeEndian = binary.BigEndian
default:
panic("Could not determine native endianness.")
}
vMajor, vMinor, vPatch, initErr = opensslInit(file)
})
return initErr
}
func utoa(n uint) string {
return strconv.FormatUint(uint64(n), 10)
}
func errUnsupportedVersion() error {
return errors.New("openssl: OpenSSL version: " + utoa(vMajor) + "." + utoa(vMinor) + "." + utoa(vPatch))
}
// checkMajorVersion panics if the current major version is not expected.
func checkMajorVersion(expected uint) {
if vMajor != expected {
panic("openssl: incorrect major version (" + strconv.Itoa(int(vMajor)) + "), expected " + strconv.Itoa(int(expected)))
}
}
type fail string
func (e fail) Error() string { return "openssl: " + string(e) + " failed" }
// VersionText returns the version text of the OpenSSL currently loaded.
func VersionText() string {
return C.GoString(C.go_openssl_OpenSSL_version(0))
}
var (
providerNameFips = C.CString("fips")
providerNameDefault = C.CString("default")
propFIPS = C.CString("fips=yes")
propNoFIPS = C.CString("-fips")
algorithmSHA256 = C.CString("SHA2-256")
)
// FIPS returns true if OpenSSL is running in FIPS mode and there is
// a provider available that supports FIPS. It returns false otherwise.
func FIPS() bool {
switch vMajor {
case 1:
return C.go_openssl_FIPS_mode() == 1
case 3:
// Check if the default properties contain `fips=1`.
if C.go_openssl_EVP_default_properties_is_fips_enabled(nil) != 1 {
// Note that it is still possible that the provider used by default is FIPS-compliant,
// but that wouldn't be a system or user requirement.
return false
}
// Check if the SHA-256 algorithm is available. If it is, then we can be sure that there is a provider available that matches
// the `fips=1` query. Most notably, this works for the common case of using the built-in FIPS provider.
//
// Note that this approach has a small chance of false negative if the FIPS provider doesn't provide the SHA-256 algorithm,
// but that is highly unlikely because SHA-256 is one of the most common algorithms and fundamental to many cryptographic operations.
// It also has a small chance of false positive if the FIPS provider implements the SHA-256 algorithm but not the other algorithms
// used by the caller application, but that is also unlikely because the FIPS provider should provide all common algorithms.
return proveSHA256(nil)
default:
panic(errUnsupportedVersion())
}
}
// isProviderAvailable checks if the provider with the given name is available.
// This function is used in export_test.go, but must be defined here as test files can't access C functions.
func isProviderAvailable(name string) bool {
if vMajor == 1 {
return false
}
providerName := C.CString(name)
defer C.free(unsafe.Pointer(providerName))
return C.go_openssl_OSSL_PROVIDER_available(nil, providerName) == 1
}
// SetFIPS enables or disables FIPS mode.
//
// For OpenSSL 3, if there is no provider available that supports FIPS mode,
// SetFIPS will try to load a built-in provider that supports FIPS mode.
func SetFIPS(enable bool) error {
if FIPS() == enable {
// Already in the desired state.
return nil
}
var mode C.int
if enable {
mode = C.int(1)
} else {
mode = C.int(0)
}
switch vMajor {
case 1:
if C.go_openssl_FIPS_mode_set(mode) != 1 {
return newOpenSSLError("FIPS_mode_set")
}
return nil
case 3:
var shaProps, provName *C.char
if enable {
shaProps = propFIPS
provName = providerNameFips
} else {
shaProps = propNoFIPS
provName = providerNameDefault
}
if !proveSHA256(shaProps) {
// There is no provider available that supports the desired FIPS mode.
// Try to load the built-in provider associated with the given mode.
if C.go_openssl_OSSL_PROVIDER_try_load(nil, provName, 1) == nil {
// The built-in provider was not loaded successfully, we can't enable FIPS mode.
C.go_openssl_ERR_clear_error()
return errors.New("openssl: FIPS mode not supported by any provider")
}
}
if C.go_openssl_EVP_default_properties_enable_fips(nil, mode) != 1 {
return newOpenSSLError("EVP_default_properties_enable_fips")
}
return nil
default:
panic(errUnsupportedVersion())
}
}
// proveSHA256 checks if the SHA-256 algorithm is available
// using the given properties.
func proveSHA256(props *C.char) bool {
md := C.go_openssl_EVP_MD_fetch(nil, algorithmSHA256, props)
if md == nil {
C.go_openssl_ERR_clear_error()
return false
}
C.go_openssl_EVP_MD_free(md)
return true
}
// noescape hides a pointer from escape analysis. noescape is
// the identity function but escape analysis doesn't think the
// output depends on the input. noescape is inlined and currently
// compiles down to zero instructions.
// USE CAREFULLY!
//
//go:nosplit
func noescape(p unsafe.Pointer) unsafe.Pointer {
x := uintptr(p)
return unsafe.Pointer(x ^ 0)
}
var zero byte
// addr converts p to its base addr, including a noescape along the way.
// If p is nil, addr returns a non-nil pointer, so that the result can always
// be dereferenced.
//
//go:nosplit
func addr(p []byte) *byte {
if len(p) == 0 {
return &zero
}
return (*byte)(noescape(unsafe.Pointer(&p[0])))
}
// base returns the address of the underlying array in b,
// being careful not to panic when b has zero length.
func base(b []byte) *C.uchar {
if len(b) == 0 {
return nil
}
return (*C.uchar)(unsafe.Pointer(&b[0]))
}
func sbase(b []byte) *C.char {
if len(b) == 0 {
return nil
}
return (*C.char)(unsafe.Pointer(&b[0]))
}
func newOpenSSLError(msg string) error {
var b strings.Builder
b.WriteString(msg)
b.WriteString("\nopenssl error(s):")
for {
var (
e C.ulong
file *C.char
line C.int
)
switch vMajor {
case 1:
e = C.go_openssl_ERR_get_error_line(&file, &line)
case 3:
e = C.go_openssl_ERR_get_error_all(&file, &line, nil, nil, nil)
default:
panic(errUnsupportedVersion())
}
if e == 0 {
break
}
b.WriteByte('\n')
var buf [256]byte
C.go_openssl_ERR_error_string_n(e, (*C.char)(unsafe.Pointer(&buf[0])), C.size_t(len(buf)))
b.WriteString(string(buf[:]) + "\n\t" + C.GoString(file) + ":" + strconv.Itoa(int(line)))
}
return errors.New(b.String())
}
var unknownFile = "<go code>\000"
// caller reports file and line number information about function invocations on
// the calling goroutine's stack, in a form suitable for passing to C code.
// The argument skip is the number of stack frames to ascend, with 0 identifying
// the caller of caller. The return values report the file name and line number
// within the file of the corresponding call. The returned file is a C string
// with static storage duration.
func caller(skip int) (file *C.char, line C.int) {
_, f, l, ok := runtime.Caller(skip + 1)
if !ok {
f = unknownFile
}
// The underlying bytes of the file string are null-terminated rodata with
// static lifetimes, so can be safely passed to C without worrying about
// leaking memory or use-after-free.
return (*C.char)(noescape(unsafe.Pointer(unsafe.StringData(f)))), C.int(l)
}
// cryptoMalloc allocates n bytes of memory on the OpenSSL heap, which may be
// different from the heap which C.malloc allocates on. The allocated object
// must be freed using cryptoFree. cryptoMalloc is equivalent to the
// OPENSSL_malloc macro.
//
// Like C.malloc, this function is guaranteed to never return nil. If OpenSSL's
// malloc indicates out of memory, it crashes the program.
//
// Only objects which the OpenSSL library will take ownership of (i.e. will be
// freed by OPENSSL_free / CRYPTO_free) need to be allocated on the OpenSSL
// heap.
func cryptoMalloc(n int) unsafe.Pointer {
file, line := caller(1)
var p unsafe.Pointer
if vMajor == 1 && vMinor == 0 {
p = C.go_openssl_CRYPTO_malloc_legacy102(C.int(n), file, line)
} else {
p = C.go_openssl_CRYPTO_malloc(C.size_t(n), file, line)
}
if p == nil {
// Un-recover()-ably crash the program in the same manner as the
// C.malloc() wrapper function.
runtime_throw("openssl: CRYPTO_malloc failed")
}
return p
}
// cryptoFree frees an object allocated on the OpenSSL heap, which may be
// different from the heap which C.malloc allocates on. cryptoFree is equivalent
// to the OPENSSL_free macro.
func cryptoFree(p unsafe.Pointer) {
if vMajor == 1 && vMinor == 0 {
C.go_openssl_CRYPTO_free_legacy102(p)
return
}
file, line := caller(1)
C.go_openssl_CRYPTO_free(p, file, line)
}
const wordBytes = bits.UintSize / 8
// Reverse each limb of z.
func (z BigInt) byteSwap() {
for i, d := range z {
var n uint = 0
for j := range wordBytes {
n |= uint(byte(d)) << (8 * (wordBytes - j - 1))
d >>= 8
}
z[i] = n
}
}
func wbase(b BigInt) *C.uchar {
if len(b) == 0 {
return nil
}
return (*C.uchar)(unsafe.Pointer(&b[0]))
}
// bignum_st_1_0_2 is bignum_st (BIGNUM) memory layout in OpenSSL 1.0.2.
type bignum_st_1_0_2 struct {
d unsafe.Pointer // Pointer to an array of BN_ULONG bit chunks
top C.int // Index of last used d +1
dmax C.int
neg C.int
flags C.int
}
func bigToBN(x BigInt) C.GO_BIGNUM_PTR {
if len(x) == 0 {
return nil
}
if vMajor == 1 && vMinor == 0 {
// OpenSSL 1.0.x does not export bn_lebin2bn on all platforms,
// so we have to emulate it.
bn := C.go_openssl_BN_new()
if bn == nil {
return nil
}
if C.go_openssl_bn_expand2(bn, C.int(len(x))) == nil {
C.go_openssl_BN_free(bn)
panic(newOpenSSLError("BN_expand2"))
}
// The bytes of a BigInt are laid out in memory in the same order as a
// BIGNUM, regardless of host endianness.
bns := (*bignum_st_1_0_2)(unsafe.Pointer(bn))
d := unsafe.Slice((*uint)(bns.d), len(x))
bns.top = C.int(copy(d, x))
return bn
}
if nativeEndian == binary.BigEndian {
z := make(BigInt, len(x))
copy(z, x)
z.byteSwap()
x = z
}
// Limbs are always ordered in LSB first, so we can safely apply
// BN_lebin2bn regardless of host endianness.
return C.go_openssl_BN_lebin2bn(wbase(x), C.int(len(x)*wordBytes), nil)
}
func bnToBig(bn C.GO_BIGNUM_PTR) BigInt {
if bn == nil {
return nil
}
if vMajor == 1 && vMinor == 0 {
// OpenSSL 1.0.x does not export bn_bn2lebinpad on all platforms,
// so we have to emulate it.
bns := (*bignum_st_1_0_2)(unsafe.Pointer(bn))
d := unsafe.Slice((*uint)(bns.d), bns.top)
x := make(BigInt, len(d))
copy(x, d)
return x
}
// Limbs are always ordered in LSB first, so we can safely apply
// BN_bn2lebinpad regardless of host endianness.
x := make(BigInt, C.go_openssl_BN_num_bits(bn))
if C.go_openssl_BN_bn2lebinpad(bn, wbase(x), C.int(len(x)*wordBytes)) == 0 {
panic("openssl: bignum conversion failed")
}
if nativeEndian == binary.BigEndian {
x.byteSwap()
}
return x
}
func bnNumBytes(bn C.GO_BIGNUM_PTR) int {
return (int(C.go_openssl_BN_num_bits(bn)) + 7) / 8
}
// bnToBinPad converts the absolute value of bn into big-endian form and stores
// it at to, padding with zeroes if necessary. If len(to) is not large enough to
// hold the result, an error is returned.
func bnToBinPad(bn C.GO_BIGNUM_PTR, to []byte) error {
if vMajor == 1 && vMinor == 0 {
// OpenSSL 1.0.x does not export bn_bn2binpad on all platforms,
// so we have to emulate it.
n := bnNumBytes(bn)
pad := len(to) - n
if pad < 0 {
return errors.New("openssl: destination buffer too small")
}
for i := range pad {
to[i] = 0
}
if int(C.go_openssl_BN_bn2bin(bn, base(to[pad:]))) != n {
return errors.New("openssl: BN_bn2bin short write")
}
return nil
}
if C.go_openssl_BN_bn2binpad(bn, base(to), C.int(len(to))) < 0 {
return newOpenSSLError("BN_bn2binpad")
}
return nil
}
func CheckLeaks() {
C.go_openssl_do_leak_check()
}
// versionAtOrAbove returns true when
// (vMajor, vMinor, vPatch) >= (major, minor, patch),
// compared lexicographically.
func versionAtOrAbove(major, minor, patch uint) bool {
return vMajor > major || (vMajor == major && vMinor > minor) || (vMajor == major && vMinor == minor && vPatch >= patch)
}