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 }