Skip to content

Add custom error type for crypto backend errors #1649

@samiponkanenssh

Description

@samiponkanenssh

I would like to start a discussion about the need for typed errors from the crypto backends.

The use case I have is detecting whether an error was triggered by OpenSSL. Currently golang-fips/openssl extracts the error string from OpenSSL and then calls errors.New() to create the error. This means that the only way to find out if error was triggered by OpenSSL is to do matching on the error strings. I find that approach error prone and not future proof.

Instead, I would find the following very handy:

Firstly, in microsoft/go, define a CryptoBackendError interface:
src/crypto/boring/boring.go:

type CryptoBackendError interface {
	error
	CryptoBackend() string
}

Next, in golang-fips/openssl, define a OpenSSLError type and wrap errors to it (note: this is not a complete diff):

diff -urN go1.23.8-1.orig/src/vendor/github.com/golang-fips/openssl/v2/openssl.go go1.23.8-1.cryptobackend-error/src/vendor/github.com/golang-fips/openssl/v2/openssl.go
--- go1.23.8-1.orig/src/vendor/github.com/golang-fips/openssl/v2/openssl.go      2025-04-22 09:34:23.824810048 +0300
+++ go1.23.8-1.cryptobackend-error/src/vendor/github.com/golang-fips/openssl/v2/openssl.go  2025-04-22 14:20:40.295540401 +0300
@@ -74,12 +74,21 @@
 }
 
 func errUnsupportedVersion() error {
-       return errors.New("openssl: OpenSSL version: " + utoa(vMajor) + "." + utoa(vMinor) + "." + utoa(vPatch))
+       return &openSSLError{err: errors.New("openssl: OpenSSL version: " + utoa(vMajor) + "." + utoa(vMinor) + "." + utoa(vPatch))}
 }
 
 type fail string
 
-func (e fail) Error() string { return "openssl: " + string(e) + " failed" }
+func (e fail) Error() string         { return "openssl: " + string(e) + " failed" }
+func (e fail) CryptoBackend() string { return "opensslcrypto" }
+
+type openSSLError struct {
+       err error
+}
+
+func (e openSSLError) Error() string         { return e.err.Error() }
+func (e openSSLError) Unwrap() error         { return e.err }
+func (e openSSLError) CryptoBackend() string { return "opensslcrypto" }
 
 // VersionText returns the version text of the OpenSSL currently loaded.
 func VersionText() string {
@@ -260,7 +269,7 @@
                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())
+       return &openSSLError{err: errors.New(b.String())}
 }
 
 var unknownFile = "<go code>\000"

Finally, in the application use errors.As() to check if an error is from OpenSSL:

var cryptoBackendErr boring.CryptoBackendError
if errors.As(err, &cryptoBackendErr) {
...

This is not a trivial change, as a) more work would be needed to convert all errors returned in golang-fips/openssl and 2) microsoft/go-crypto-winnative should eventually be converted, for the sake of completeness. The bright side though is, that the work could be done in multiple phases and independently for each crypto backend.

Thoughts?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions