Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add bindings for DANE #14

Merged
merged 1 commit into from
Aug 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)))
}
24 changes: 24 additions & 0 deletions cert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package openssl

import (
"encoding/hex"
"math/big"
"testing"
"time"
Expand Down Expand Up @@ -162,3 +163,26 @@ func TestCertVersion(t *testing.T) {
t.Fatalf("bad version: %d", vers)
}
}

func TestCertHash(t *testing.T) {
cert, err := LoadCertificateFromPEM(certBytes)
if err != nil {
t.Fatal(err)
}

digest, err := GetDigestByName("SHA256")
if err != nil {
t.Fatal(err)
}

if hash := hex.EncodeToString(cert.Hash(digest)); hash != certHashHex {
t.Fatalf("Wrong hash returned, expected %q, got %q", certHashHex, hash)
}
}

func TestVerifyCertErrorString(t *testing.T) {
expected := "unable to get issuer certificate"
if result := VerifyCertErrorString(UnableToGetIssuerCert); result != expected {
t.Fatalf("Wrong cert error string returned, expected %q, got %q", expected, 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
)
DerekBum marked this conversation as resolved.
Show resolved Hide resolved

// 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
// (bool 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 given flags for this connection. Returns previous flags.
// 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. Returns previous flags.
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
Loading
Loading