Skip to content

Commit

Permalink
Add bindings for DANE and handshake tracing.
Browse files Browse the repository at this point in the history
  • Loading branch information
coma64 committed Aug 30, 2024
1 parent 576e43d commit 0f86b1e
Show file tree
Hide file tree
Showing 9 changed files with 171 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
openssl.test
/.ccls-cache/
/.ccls
/.idea/
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
24 changes: 24 additions & 0 deletions cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)))
}
32 changes: 32 additions & 0 deletions ctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)))
}
3 changes: 2 additions & 1 deletion digest.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
10 changes: 10 additions & 0 deletions shim.c
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
2 changes: 2 additions & 0 deletions shim.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include <openssl/bio.h>
#include <openssl/crypto.h>
Expand Down Expand Up @@ -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();
Expand Down
91 changes: 91 additions & 0 deletions ssl.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import "C"

import (
"os"
"runtime"
"unsafe"

"github.com/mattn/go-pointer"
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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() {
Expand Down

0 comments on commit 0f86b1e

Please sign in to comment.