Skip to content

Commit 8ee8c4b

Browse files
committed
Enable building on Unix and Windows
The Linux limitation is somewhat artificial. The only platform-specific parts of the module are dynamic linking and loading of the OpenSSL library, and the OpenSSL 1.0.2 threading callbacks. The implementations of both are already portable to any Unix-like OS as dlfcn.h and pthreads are part of the POSIX standard. The only issue with the dynamic loader implementation is that it assumes the naming convention for the library shared object is "libcrypto.so.<version>" which limits support for darwin and other platforms which follow a different convention. Change openssl.Init() and openssl.CheckVersion() to take the library file name as its argument to make the bindings agnostic to the target platform's library file naming convention. Widen the build constraints to allow the module with the dlopen()-based loader to be built on any OS which satisfies the "unix" Go build constraint. Windows's dynamic library loader API is shaped very similarly to POSIX's, aside from the function names. Add support for Windows by adding Windows implementations for linking and loading the OpenSSL library, and the threading callbacks. Signed-off-by: Cory Snider <[email protected]>
1 parent 758238f commit 8ee8c4b

32 files changed

+159
-73
lines changed

README.md

+1-3
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,7 @@ This feature does not require any additional configuration, but it only works wi
5050

5151
## Limitations
5252

53-
OpenSSL is used for a given build only in limited circumstances:
54-
55-
- The platform must be `GOOS=linux`.
53+
- Only Unix, Unix-like and Windows platforms are supported.
5654
- The build must set `CGO_ENABLED=1`.
5755

5856
## Acknowledgements

aes.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build linux && !cmd_go_bootstrap
1+
//go:build !cmd_go_bootstrap
22

33
package openssl
44

aes_test.go

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
//go:build linux
2-
31
package openssl
42

53
import (

ec.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build linux && !cmd_go_bootstrap
1+
//go:build !cmd_go_bootstrap
22

33
package openssl
44

ecdh.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build linux && !cmd_go_bootstrap
1+
//go:build !cmd_go_bootstrap
22

33
package openssl
44

ecdh_test.go

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
//go:build linux
2-
31
package openssl_test
42

53
import (

ecdsa.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build linux && !cmd_go_bootstrap
1+
//go:build !cmd_go_bootstrap
22

33
package openssl
44

ecdsa_test.go

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
//go:build linux
2-
31
package openssl_test
42

53
import (

evp.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build linux && !cmd_go_bootstrap
1+
//go:build !cmd_go_bootstrap
22

33
package openssl
44

goopenssl.c

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1-
//go:build linux
1+
//go:build unix || windows
22

33
#include "goopenssl.h"
44

5-
#include <dlfcn.h> // dlsym
5+
#ifdef _WIN32
6+
# include <windows.h>
7+
# define dlsym (void*)GetProcAddress
8+
#else
9+
# include <dlfcn.h> // dlsym
10+
#endif
611
#include <stdio.h> // fprintf
712

813
// Approach taken from .Net System.Security.Cryptography.Native

hkdf.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build linux && !cmd_go_bootstrap
1+
//go:build !cmd_go_bootstrap
22

33
package openssl
44

hkdf_test.go

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
//go:build linux
2-
31
package openssl_test
42

53
import (

hmac.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build linux && !cmd_go_bootstrap
1+
//go:build !cmd_go_bootstrap
22

33
package openssl
44

hmac_test.go

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
//go:build linux
2-
31
package openssl
42

53
import (

init.go

+6-15
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,23 @@
1-
//go:build linux && !cmd_go_bootstrap
1+
//go:build !cmd_go_bootstrap
22

33
package openssl
44

55
// #include "goopenssl.h"
6-
// #include <dlfcn.h>
76
import "C"
87
import (
98
"errors"
10-
"unsafe"
119
)
1210

1311
// opensslInit loads and initialize OpenSSL.
1412
// If successful, it returns the major and minor OpenSSL version
1513
// as reported by the OpenSSL API.
1614
//
17-
// See Init() for details about version.
18-
func opensslInit(version string) (major, minor, patch int, err error) {
15+
// See Init() for details about file.
16+
func opensslInit(file string) (major, minor, patch int, err error) {
1917
// Load the OpenSSL shared library using dlopen.
20-
handle := dlopen(version)
21-
if handle == nil {
22-
errstr := C.GoString(C.dlerror())
23-
return 0, 0, 0, errors.New("openssl: can't load libcrypto.so." + version + ": " + errstr)
18+
handle, err := dlopen(file)
19+
if err != nil {
20+
return 0, 0, 0, err
2421
}
2522

2623
// Retrieve the loaded OpenSSL version and check if it is supported.
@@ -64,9 +61,3 @@ func opensslInit(version string) (major, minor, patch int, err error) {
6461
}
6562
return major, minor, patch, nil
6663
}
67-
68-
func dlopen(version string) unsafe.Pointer {
69-
cv := C.CString("libcrypto.so." + version)
70-
defer C.free(unsafe.Pointer(cv))
71-
return C.dlopen(cv, C.RTLD_LAZY|C.RTLD_LOCAL)
72-
}

init_unix.go

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//go:build unix && !cmd_go_bootstrap
2+
3+
package openssl
4+
5+
// #cgo LDFLAGS: -ldl
6+
// #include <stdlib.h>
7+
// #include <dlfcn.h>
8+
import "C"
9+
import (
10+
"errors"
11+
"unsafe"
12+
)
13+
14+
func dlopen(file string) (handle unsafe.Pointer, err error) {
15+
cv := C.CString(file)
16+
defer C.free(unsafe.Pointer(cv))
17+
handle = C.dlopen(cv, C.RTLD_LAZY|C.RTLD_LOCAL)
18+
if handle == nil {
19+
errstr := C.GoString(C.dlerror())
20+
return nil, errors.New("openssl: can't load " + file + ": " + errstr)
21+
}
22+
return handle, nil
23+
}
24+
25+
func dlclose(handle unsafe.Pointer) error {
26+
if C.dlclose(handle) != 0 {
27+
errstr := C.GoString(C.dlerror())
28+
return errors.New("openssl: can't close libcrypto: " + errstr)
29+
}
30+
return nil
31+
}

init_windows.go

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//go:build !cmd_go_bootstrap
2+
3+
package openssl
4+
5+
import (
6+
"syscall"
7+
"unsafe"
8+
)
9+
10+
type dlopenError struct {
11+
file string
12+
err error
13+
}
14+
15+
func (e *dlopenError) Error() string {
16+
return "openssl: can't load " + e.file + ": " + e.err.Error()
17+
}
18+
19+
func (e *dlopenError) Unwrap() error {
20+
return e.err
21+
}
22+
23+
func dlopen(file string) (handle unsafe.Pointer, err error) {
24+
// As Windows generally does not ship with a system OpenSSL library, let
25+
// alone a FIPS 140 certified one, use the default library search order so
26+
// that we preferentially load the DLL bundled with the application.
27+
h, err := syscall.LoadLibrary(file)
28+
if err != nil {
29+
return nil, &dlopenError{file: file, err: err}
30+
}
31+
return unsafe.Pointer(h), nil
32+
}
33+
34+
func dlclose(handle unsafe.Pointer) error {
35+
return syscall.FreeLibrary(syscall.Handle(handle))
36+
}

openssl.go

+11-13
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
//go:build linux && !cmd_go_bootstrap
1+
//go:build !cmd_go_bootstrap
22

33
// Package openssl provides access to OpenSSL cryptographic functions.
44
package openssl
55

66
// #include "goopenssl.h"
7-
// #include <dlfcn.h>
8-
// #cgo LDFLAGS: -ldl
97
import "C"
108
import (
119
"encoding/binary"
@@ -34,25 +32,25 @@ var nativeEndian binary.ByteOrder
3432
// and if the FIPS mode is enabled.
3533
// This function can be called before Init.
3634
func CheckVersion(version string) (exists, fips bool) {
37-
handle := dlopen(version)
35+
handle, _ := dlopen(version)
3836
if handle == nil {
3937
return false, false
4038
}
41-
defer C.dlclose(handle)
39+
defer dlclose(handle)
4240
fips = C.go_openssl_fips_enabled(handle) == 1
4341
return true, fips
4442
}
4543

46-
// Init loads and initializes OpenSSL.
44+
// Init loads and initializes OpenSSL from the shared library at path.
4745
// It must be called before any other OpenSSL call, except CheckVersion.
4846
//
49-
// Only the first call to Init is effective,
50-
// subsequent calls will return the same error result as the one from the first call.
47+
// Only the first call to Init is effective.
48+
// Subsequent calls will return the same error result as the one from the first call.
5149
//
52-
// version will be appended to the OpenSSL shared library name as a version suffix
53-
// when calling dlopen. For example, `version=1.1.1k-fips` makes Init look for
54-
// the shared library libcrypto.so.1.1.1k-fips.
55-
func Init(version string) error {
50+
// The file is passed to dlopen() verbatim to load the OpenSSL shared library.
51+
// For example, `file=libcrypto.so.1.1.1k-fips` makes Init look for the shared
52+
// library libcrypto.so.1.1.1k-fips.
53+
func Init(file string) error {
5654
initOnce.Do(func() {
5755
buf := [2]byte{}
5856
*(*uint16)(unsafe.Pointer(&buf[0])) = uint16(0xABCD)
@@ -65,7 +63,7 @@ func Init(version string) error {
6563
default:
6664
panic("Could not determine native endianness.")
6765
}
68-
vMajor, vMinor, vPatch, initErr = opensslInit(version)
66+
vMajor, vMinor, vPatch, initErr = opensslInit(file)
6967
})
7068
return initErr
7169
}

openssl_test.go

+21-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
//go:build linux
2-
31
package openssl_test
42

53
import (
@@ -16,22 +14,40 @@ import (
1614
func getVersion() string {
1715
v := os.Getenv("GO_OPENSSL_VERSION_OVERRIDE")
1816
if v != "" {
17+
if runtime.GOOS == "linux" {
18+
return "libcrypto.so." + v
19+
}
1920
return v
2021
}
2122
// Try to find a supported version of OpenSSL on the system.
2223
// This is useful for local testing, where the user may not
2324
// have GO_OPENSSL_VERSION_OVERRIDE set.
24-
for _, v = range [...]string{"3", "1.1.1", "1.1", "11", "111", "1.0.2", "1.0.0", "10"} {
25+
versions := []string{"3", "1.1.1", "1.1", "11", "111", "1.0.2", "1.0.0", "10"}
26+
if runtime.GOOS == "windows" {
27+
if runtime.GOARCH == "amd64" {
28+
versions = []string{"libcrypto-3-x64", "libcrypto-3", "libcrypto-1_1-x64", "libcrypto-1_1", "libeay64", "libeay32"}
29+
} else {
30+
versions = []string{"libcrypto-3", "libcrypto-1_1", "libeay32"}
31+
}
32+
}
33+
for _, v = range versions {
34+
if runtime.GOOS == "windows" {
35+
v += ".dll"
36+
} else if runtime.GOOS == "darwin" {
37+
v = "libcrypto." + v + ".dylib"
38+
} else {
39+
v = "libcrypto.so." + v
40+
}
2541
if ok, _ := openssl.CheckVersion(v); ok {
2642
return v
2743
}
2844
}
29-
return ""
45+
return "libcrypto.so"
3046
}
3147

3248
func TestMain(m *testing.M) {
3349
v := getVersion()
34-
fmt.Printf("Using libcrypto.so.%s\n", v)
50+
fmt.Printf("Using %s\n", v)
3551
err := openssl.Init(v)
3652
if err != nil {
3753
// An error here could mean that this Linux distro does not have a supported OpenSSL version

pbkdf2.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build linux && !cmd_go_bootstrap
1+
//go:build !cmd_go_bootstrap
22

33
package openssl
44

pbkdf2_test.go

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
//go:build linux
2-
31
package openssl_test
42

53
import (

port_evp_md5_sha1.c

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
//go:build linux
2-
31
// The following is a partial backport of crypto/evp/m_md5_sha1.c,
42
// commit cbc8a839959418d8a2c2e3ec6bdf394852c9501e on the
53
// OpenSSL_1_1_0-stable branch. The ctrl function has been removed.

rand.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build linux && !cmd_go_bootstrap
1+
//go:build !cmd_go_bootstrap
22

33
package openssl
44

rand_test.go

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
//go:build linux
2-
31
package openssl_test
42

53
import (

rsa.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build linux && !cmd_go_bootstrap
1+
//go:build !cmd_go_bootstrap
22

33
package openssl
44

rsa_test.go

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
//go:build linux
2-
31
package openssl_test
42

53
import (

sha.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build linux && !cmd_go_bootstrap
1+
//go:build !cmd_go_bootstrap
22

33
package openssl
44

sha_test.go

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
//go:build linux
2-
31
package openssl_test
42

53
import (

thread_setup.c renamed to thread_setup_unix.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build linux
1+
//go:build unix
22

33
#include "goopenssl.h"
44
#include <pthread.h>

0 commit comments

Comments
 (0)