github.com/IBM-Bluemix/golang-openssl-wrapper@v0.0.0-20160104220506-7f2d5273b515/ssl/httpsclient.go (about)

     1  package ssl
     2  
     3  /*
     4   * Module: httpsclient.go
     5   */
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"github.com/IBM-Bluemix/golang-openssl-wrapper/bio"
    11  	"net"
    12  	"net/http"
    13  	"net/url"
    14  	"strings"
    15  	"time"
    16  )
    17  
    18  var networksAllowed = map[string]bool{
    19  	"tcp":  true,
    20  	"tcp4": true,
    21  	"tcp6": true,
    22  	"ip":   true,
    23  	"ip4":  true,
    24  	"ip6":  true,
    25  }
    26  
    27  // HTTPSConn extends net.Conn to provide HTTPS functions using OpenSSL.
    28  type HTTPSConn struct {
    29  	net.Conn
    30  	desthost   string
    31  	connected  bool
    32  	ctx        SSL_CTX
    33  	sslInst    SSL
    34  	sslBio     bio.BIO
    35  	remoteAddr *net.TCPAddr
    36  	localAddr  *net.TCPAddr
    37  }
    38  
    39  // Read reads n bytes from the connection into b.
    40  // Read returns the number of bytes read or 0 and an error if the underlying read fails.
    41  func (h HTTPSConn) Read(b []byte) (n int, err error) {
    42  	ret := bio.BIO_read(h.sslBio, b, len(b))
    43  	if ret < 0 {
    44  		return ret, fmt.Errorf("Possible socket read error - got %d from BIO_read()", ret)
    45  	}
    46  	return ret, nil
    47  }
    48  
    49  // Write writes n bytes from b onto the connection.
    50  // Write returns the number of bytes written and any error that occurred.
    51  func (h HTTPSConn) Write(b []byte) (n int, err error) {
    52  	ret := bio.BIO_write(h.sslBio, string(b), len(b))
    53  	if ret != len(b) {
    54  		return ret, fmt.Errorf("SSL socket write failed; only %d bytes written out of %d", ret, len(b))
    55  	}
    56  
    57  	return ret, nil
    58  }
    59  
    60  // Close closes the underlying connection.
    61  // Close will return an error if it is invoked on a partially or already closed connection.
    62  func (h HTTPSConn) Close() error {
    63  	if (h.ctx != nil) && (h.sslInst != nil) && (h.sslBio != nil) {
    64  		SSL_CTX_free(h.ctx)
    65  		h.ctx = nil
    66  		SSL_free(h.sslInst)
    67  		h.sslInst = nil
    68  		bio.BIO_free_all(h.sslBio)
    69  		h.sslBio = nil
    70  		return nil
    71  	}
    72  
    73  	if (h.ctx != nil) || (h.sslInst != nil) || (h.sslBio != nil) {
    74  		return errors.New("HTTPSConn in partially closed state, not all objects freed, unable to close further")
    75  	}
    76  
    77  	return errors.New("Attempted to close already closed HTTPSConn")
    78  }
    79  
    80  // LocalAddr returns the local address as a net.Addr.
    81  func (h HTTPSConn) LocalAddr() net.Addr {
    82  	return h.localAddr
    83  }
    84  
    85  // RemoteAddr returns the remote address for the connection as a net.Addr.
    86  func (h HTTPSConn) RemoteAddr() net.Addr {
    87  	return h.remoteAddr
    88  }
    89  
    90  func validateDeadline(t time.Time) error {
    91  	now := time.Now()
    92  	if t.Equal(now) || t.Before(now) {
    93  		return errors.New("Invalid deadline")
    94  	}
    95  
    96  	if t.After(now.Add(time.Duration(10) * time.Minute)) {
    97  		return errors.New("Deadline beyond allowed horizon")
    98  	}
    99  
   100  	return nil
   101  }
   102  
   103  // TODO: implement Set[{Read,Write}]Deadline
   104  
   105  // // SetDeadLine sets the deadline for both reads and writes.
   106  // // t should be a time.Time representing a relative interval, such as 10 minutes.
   107  // // SetDeadline, SetReadDeadline, and SetWriteDeadLine will all return an error
   108  // // if t equals the current time or is before it.
   109  // // They will also return an error for an interval longer than 10 minutes.
   110  // func (h HTTPSConn) SetDeadLine(t time.Time) error {
   111  // 	return validateDeadline(t)
   112  // }
   113  
   114  // // SetReadDeadLine sets the deadline for reads.
   115  // func (h HTTPSConn) SetReadDeadLine(t time.Time) error {
   116  // 	return validateDeadline(t)
   117  // }
   118  
   119  // // SetWriteDeadLine sets the deadline for writes.
   120  // func (h HTTPSConn) SetWriteDeadLine(t time.Time) error {
   121  // 	return validateDeadline(t)
   122  // }
   123  
   124  /*
   125   * Setup the Transport
   126   */
   127  
   128  func dial(network, addr string) (net.Conn, error) {
   129  	return dialTLS(network, addr)
   130  }
   131  
   132  /*
   133   * dialTLS() Returns an httpsclient.HTTPSConn instance.
   134   * We inject the BIO object for use in I/O.
   135   * We inject the CTX and SSL objects for use in connection
   136   * management.
   137   */
   138  func dialTLS(network, addr string) (net.Conn, error) {
   139  	var err error
   140  	var ctx SSL_CTX
   141  	var conn bio.BIO
   142  	var dest, dhost, dport string
   143  	var ra *net.TCPAddr
   144  
   145  	if !networksAllowed[network] {
   146  		return nil, fmt.Errorf("Invalid network specified: %q", network)
   147  	}
   148  
   149  	cc := strings.Count(addr, ":")
   150  	switch {
   151  	case cc == 0:
   152  		/* Default is port 443 */
   153  		dhost = addr
   154  		dport = "443"
   155  	case cc == 1:
   156  		dhost, dport, err = net.SplitHostPort(addr)
   157  		if err != nil {
   158  			return nil, errors.New("Unable to parse address")
   159  		}
   160  	case cc > 1:
   161  		return nil, errors.New("Invalid address specified")
   162  	}
   163  	dest = net.JoinHostPort(dhost, dport)
   164  
   165  	ra, err = net.ResolveTCPAddr(network, dest)
   166  	if err != nil {
   167  		return nil, fmt.Errorf("Unable to resolve address %s on network %s", dest, network)
   168  	}
   169  
   170  	ctx, err = ctxInit("", SSLv23_client_method())
   171  	if err != nil {
   172  		return nil, err
   173  	}
   174  
   175  	conn, err = sslInit(ctx, dest)
   176  	if err != nil {
   177  		return nil, err
   178  	}
   179  
   180  	err = connect(conn)
   181  	if err != nil {
   182  		return nil, err
   183  	}
   184  
   185  	h := HTTPSConn{
   186  		desthost:   addr,
   187  		ctx:        ctx,
   188  		sslBio:     conn,
   189  		remoteAddr: ra,
   190  	}
   191  	return h, nil
   192  
   193  }
   194  
   195  // NewHTTPSClient returns an http.Client configured to use OpenSSL for TLS.
   196  // The client cannot be used for non-TLS communications - use a regular http.Client instead.
   197  // This is a convenience function wrapping NewHTTPSTransport.
   198  func NewHTTPSClient() http.Client {
   199  	return http.Client{
   200  		Transport: NewHTTPSTransport(nil),
   201  	}
   202  }
   203  
   204  // NewHTTPSTransport returns an http.Transport configured to use OpenSSL for TLS.
   205  // The transport cannot be used for non-TLS communications - use a regular http.Transport instead.
   206  func NewHTTPSTransport(proxyFunc func(*http.Request) (*url.URL, error)) *http.Transport {
   207  	h := &http.Transport{
   208  		Dial:    dialTLS,
   209  		DialTLS: dialTLS,
   210  		Proxy:   proxyFunc,
   211  	}
   212  	return h
   213  }
   214  
   215  func sslInit(ctx SSL_CTX, hostname string) (bio.BIO, error) {
   216  	/* Initialize the SSL and connect BIOs */
   217  	conn := bio.BIO_new_ssl_connect(ctx)
   218  	if conn == nil {
   219  		return nil, errors.New("Unable to setup I/O")
   220  	}
   221  
   222  	if SSL_CTX_load_verify_locations(ctx, "", "/etc/ssl/certs") != 1 {
   223  		return nil, errors.New("Unable to load certificates for verification")
   224  	}
   225  	if bio.BIO_set_conn_hostname(conn, hostname) != 1 {
   226  		return nil, errors.New("Unable to set hostname in BIO object")
   227  	}
   228  
   229  	/* Setup SSL */
   230  	sslInst := SSL_new(ctx)
   231  	if sslInst == nil {
   232  		return nil, errors.New("Unable to initialize SSL")
   233  	}
   234  
   235  	if bio.BIO_get_ssl(conn, sslInst) != 1 {
   236  		return nil, errors.New("Unable to configure SSL for I/O")
   237  	}
   238  
   239  	ciphers := "HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4"
   240  	if SSL_set_cipher_list(sslInst, ciphers) != 1 {
   241  		return nil, errors.New("Unable to configure ciphers")
   242  	}
   243  
   244  	if SSL_set_tlsext_host_name(sslInst, hostname) != 1 {
   245  		return nil, errors.New("Unable to set SSL hostname")
   246  	}
   247  
   248  	return conn, nil
   249  }
   250  
   251  /* Make the connection */
   252  func connect(conn bio.BIO) error {
   253  	if bio.BIO_do_connect(conn) != 1 {
   254  		return errors.New("Unable to connect to SSL destination")
   255  	}
   256  
   257  	if bio.BIO_do_handshake(conn) != 1 {
   258  		return errors.New("Unable to complete SSL handshake")
   259  	}
   260  
   261  	return nil
   262  }