diff --git a/.gitignore b/.gitignore index 805d350b..ce68b846 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ openssl.test +/.ccls-cache/ +/.ccls +/.idea/ diff --git a/CHANGELOG.md b/CHANGELOG.md index e35e6f69..222f8f89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,12 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release. ### Added +- Bindings for [DANE](https://docs.openssl.org/1.1.1/man3/SSL_CTX_dane_enable/). +- Bindings for [TLS handshake tracing](https://docs.openssl.org/master/man3/SSL_CTX_set_msg_callback/). +- Bindings for `X509_digest()`. +- Bindings for `X509_verify_cert_error_string()`. +- Bindings for `SSL_get_version()`. + ### Changed ### Fixed diff --git a/README.md b/README.md index 7b9b6ad0..53334f3a 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ Forked from https://github.com/libp2p/openssl (unmaintained) to add: 2. Fix build on Apple M1. 3. Fix static build. 4. Fix error extraction on key reading. +5. Bindings for DANE. ### License diff --git a/cert.go b/cert.go index 97c788f7..294e450c 100644 --- a/cert.go +++ b/cert.go @@ -430,3 +430,27 @@ func (c *Certificate) GetExtensionValue(nid NID) []byte { val := C.get_extention(c.x, C.int(nid), &dataLength) return C.GoBytes(unsafe.Pointer(val), dataLength) } + +// Hash uses the given digest to generate a hash of the certificate. Use GetDigestByName +// to get a digest. +func (c *Certificate) Hash(digest *Digest) []byte { + var hashLength C.uint + hash := make([]byte, C.EVP_MAX_MD_SIZE) + + C.X509_digest(c.x, digest.ptr, (*C.uchar)(unsafe.Pointer(&hash[0])), &hashLength) + + return hash[:hashLength] +} + +// VerifyCertErrorString returns a human-readable error string for the given verification error. +// https://www.openssl.org/docs/man3.1/man3/X509_verify_cert_error_string.html +func VerifyCertErrorString(result VerifyResult) string { + // Locking the thread because the docs say: + // If an unrecognised error code is passed to X509_verify_cert_error_string() the + // numerical value of the unknown code is returned in a static buffer. This is not + // thread safe but will never happen unless an invalid code is passed. + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + return C.GoString(C.X509_verify_cert_error_string(C.long(result))) +} diff --git a/ctx.go b/ctx.go index 3bebf0d5..e8fd6b50 100644 --- a/ctx.go +++ b/ctx.go @@ -616,3 +616,35 @@ func (c *Ctx) SessSetCacheSize(t int) int { func (c *Ctx) SessGetCacheSize() int { return int(C.X_SSL_CTX_sess_get_cache_size(c.ctx)) } + +// DaneEnable initializes shared state required for DANE support. Must be +// called before any other Dane function. +// https://www.openssl.org/docs/man1.1.1/man3/SSL_dane_clear_flags.html +func (c *Ctx) DaneEnable() error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if C.SSL_CTX_dane_enable(c.ctx) <= 0 { + return errorFromErrorQueue() + } + + return nil +} + +type DaneFlags int + +const ( + DaneFlagNoDaneEeNamechecks DaneFlags = C.DANE_FLAG_NO_DANE_EE_NAMECHECKS +) + +// DaneSetFlags enables the default flags of every connection associated +// with this context. See DaneFlag for available flags. +// https://www.openssl.org/docs/man1.1.1/man3/SSL_dane_clear_flags.html +func (c *Ctx) DaneSetFlags(flags DaneFlags) DaneFlags { + return DaneFlags(C.SSL_CTX_dane_set_flags(c.ctx, C.ulong(flags))) +} + +// DaneClearFlags disables flags set by DaneSetFlags. +func (c *Ctx) DaneClearFlags(flags DaneFlags) DaneFlags { + return DaneFlags(C.SSL_CTX_dane_clear_flags(c.ctx, C.ulong(flags))) +} diff --git a/digest.go b/digest.go index 6d8d2635..66190811 100644 --- a/digest.go +++ b/digest.go @@ -28,7 +28,8 @@ type Digest struct { } // GetDigestByName returns the Digest with the name or nil and an error if the -// digest was not found. +// digest was not found. Use `openssl list -digest-algorithms` to list available +// digest names. func GetDigestByName(name string) (*Digest, error) { cname := C.CString(name) defer C.free(unsafe.Pointer(cname)) diff --git a/shim.c b/shim.c index ae951029..0939c08e 100644 --- a/shim.c +++ b/shim.c @@ -439,6 +439,16 @@ int X_SSL_verify_cb(int ok, X509_STORE_CTX* store) { return go_ssl_verify_cb_thunk(p, ok, store); } +void X_SSL_toggle_tracing(SSL* ssl, FILE* output, short enable) { + if (enable) { + SSL_set_msg_callback(ssl, SSL_trace); + SSL_set_msg_callback_arg(ssl, BIO_new_fp(output, BIO_NOCLOSE)); + } else { + SSL_set_msg_callback(ssl, NULL); + SSL_set_msg_callback_arg(ssl, NULL); + } +} + const SSL_METHOD *X_SSLv23_method() { return SSLv23_method(); } diff --git a/shim.h b/shim.h index fbd2b26b..b04c742f 100644 --- a/shim.h +++ b/shim.h @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -53,6 +54,7 @@ extern long X_SSL_set_tlsext_host_name(SSL *ssl, const char *name); extern const char * X_SSL_get_cipher_name(const SSL *ssl); extern int X_SSL_session_reused(SSL *ssl); extern int X_SSL_new_index(); +extern void X_SSL_toggle_tracing(SSL* ssl, FILE* output, short enable); extern const SSL_METHOD *X_SSLv23_method(); extern const SSL_METHOD *X_SSLv3_method(); diff --git a/ssl.go b/ssl.go index b187d15d..81cf5542 100644 --- a/ssl.go +++ b/ssl.go @@ -19,6 +19,7 @@ import "C" import ( "os" + "runtime" "unsafe" "github.com/mattn/go-pointer" @@ -92,6 +93,23 @@ func (s *SSL) ClearOptions(options Options) Options { return Options(C.X_SSL_clear_options(s.ssl, C.long(options))) } +// EnableTracing enables TLS handshake tracing using openssls +// SSL_trace function. If useStderr is false, stdout is used. +// https://www.openssl.org/docs/manmaster/man3/SSL_trace.html +func (s *SSL) EnableTracing(useStderr bool) { + output := C.stdout + if useStderr { + output = C.stderr + } + + C.X_SSL_toggle_tracing(s.ssl, output, 1); +} + +// DisableTracing unsets the msg callback from EnableTracing. +func (s *SSL) DisableTracing() { + C.X_SSL_toggle_tracing(s.ssl, nil, 0); +} + // SetVerify controls peer verification settings. See // http://www.openssl.org/docs/ssl/SSL_CTX_set_verify.html func (s *SSL) SetVerify(options VerifyOptions, verify_cb VerifyCallback) { @@ -152,6 +170,79 @@ func (s *SSL) SetSSLCtx(ctx *Ctx) { C.SSL_set_SSL_CTX(s.ssl, ctx.ctx) } +// GetVersion() returns the name of the protocol used for the connection. It +// should only be called after the initial handshake has been completed otherwise +// the result may be unreliable. +// https://www.openssl.org/docs/man1.0.2/man3/SSL_get_version.html +func (s *SSL) GetVersion() string { + return C.GoString(C.SSL_get_version(s.ssl)) +} + +// DaneEnable enables DANE validation for this connection. It must be called +// before the TLS handshake. +// https://www.openssl.org/docs/man1.1.1/man3/SSL_dane_clear_flags.html +func (s *SSL) DaneEnable(tlsaBaseDomain string) error { + tlsaBaseDomainCString := C.CString(tlsaBaseDomain) + defer C.free(unsafe.Pointer(tlsaBaseDomainCString)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if C.SSL_dane_enable(s.ssl, tlsaBaseDomainCString) <= 0 { + return errorFromErrorQueue() + } + + return nil +} + +// DaneTlsaAdd loads a TLSA record that will be validated against the presented certificate. +// Data must be in wire form, not hex ASCII. If all TLSA records you try to add are unusable +// (isUsable return value) an opportunistic application must disable peer authentication by +// using a verify mode equal to VerifyNone. +// https://www.openssl.org/docs/man1.1.1/man3/SSL_dane_clear_flags.html +func (s *SSL) DaneTlsaAdd(usage, selector, matchingType byte, data []byte) (bool, error) { + cData := C.CBytes(data) + defer C.free(cData) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if status := C.SSL_dane_tlsa_add( + s.ssl, + C.uchar(usage), + C.uchar(selector), + C.uchar(matchingType), + (*C.uchar)(cData), + C.size_t(len(data)), + ); status < 0 { + return false, errorFromErrorQueue() + } else if status == 0 { + return false, nil + } + return true, nil +} + +// DaneGet0DaneAuthority returns a value that is negative if DANE verification failed (or +// was not enabled), 0 if an EE TLSA record directly matched the leaf certificate, or a +// positive number indicating the depth at which a TA record matched an issuer certificate. +// However, the depth doesn't refer to the list of certificates as sent by the peer but rather +// how it's returned from SSL_get0_verified_chain. +// https://www.openssl.org/docs/man1.1.1/man3/SSL_dane_clear_flags.html +func (s *SSL) DaneGet0DaneAuthority() int { + return int(C.SSL_get0_dane_authority(s.ssl, nil, nil)) +} + +// DaneSetFlags enables the given flags for this connection. +// https://www.openssl.org/docs/man1.1.1/man3/SSL_dane_clear_flags.html +func (s *SSL) DaneSetFlags(flags DaneFlags) DaneFlags { + return DaneFlags(C.SSL_dane_set_flags(s.ssl, C.ulong(flags))) +} + +// DaneClearFlags disables flags set by DaneSetFlags. +func (s *SSL) DaneClearFlags(flags DaneFlags) DaneFlags { + return DaneFlags(C.SSL_dane_clear_flags(s.ssl, C.ulong(flags))) +} + //export sni_cb_thunk func sni_cb_thunk(p unsafe.Pointer, con *C.SSL, ad unsafe.Pointer, arg unsafe.Pointer) C.int { defer func() {