Skip to content

Commit 446090c

Browse files
Merge pull request #581 from merlin-northern/men_3730_all_server_communication_via_openssl
MEN-3730 all server communication via openssl
2 parents e0ceedb + 63d50ed commit 446090c

14 files changed

Lines changed: 1228 additions & 115 deletions

client/client.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"strings"
2727
"time"
2828

29+
openssl "github.com/Linutronix/golang-openssl"
2930
"github.com/pkg/errors"
3031
log "github.com/sirupsen/logrus"
3132
"golang.org/x/net/http2"
@@ -280,6 +281,49 @@ func newHttpClient() *http.Client {
280281
return &http.Client{}
281282
}
282283

284+
func dialOpenSSL(conf Config, network string, addr string) (net.Conn, error) {
285+
contextSSL, err := openssl.NewCtx()
286+
// probably should consider reusing the context, but then we
287+
// have to propagate it with every request.
288+
err = contextSSL.LoadVerifyLocations(conf.ServerCert, "")
289+
if err != nil {
290+
return nil, err
291+
}
292+
flags := openssl.DialFlags(0)
293+
294+
if conf.NoVerify {
295+
flags = openssl.InsecureSkipHostVerification
296+
}
297+
298+
conn, err := openssl.Dial("tcp", addr, contextSSL, flags)
299+
if err != nil {
300+
return nil, err
301+
}
302+
303+
v := conn.VerifyResult()
304+
if v != openssl.Ok {
305+
if v == openssl.CertHasExpired {
306+
return nil, errors.Errorf("certificate has expired, "+
307+
"openssl verify rc: %d server cert file: %s", v, conf.ServerCert)
308+
}
309+
if v == openssl.DepthZeroSelfSignedCert {
310+
return nil, errors.Errorf("depth zero self-signed certificate, "+
311+
"openssl verify rc: %d server cert file: %s", v, conf.ServerCert)
312+
}
313+
if v == 0x42 { //X509_V_ERR_EE_KEY_TOO_SMALL
314+
return nil, errors.Errorf("end entity key too short, "+
315+
"openssl verify rc: %d server cert file: %s", v, conf.ServerCert)
316+
}
317+
if v == 0x14 { //X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY
318+
return nil, errors.Errorf("certificate signed by unknown authority, "+
319+
"openssl verify rc: %d server cert file: %s", v, conf.ServerCert)
320+
}
321+
return nil, errors.Errorf("not a valid certificate, "+
322+
"openssl verify rc: %d server cert file: %s", v, conf.ServerCert)
323+
}
324+
return conn, err
325+
}
326+
283327
func newHttpsClient(conf Config) (*http.Client, error) {
284328
client := newHttpClient()
285329

@@ -295,6 +339,9 @@ func newHttpsClient(conf Config) (*http.Client, error) {
295339
transport := http.Transport{
296340
TLSClientConfig: &tlsc,
297341
Proxy: http.ProxyFromEnvironment,
342+
DialTLS: func(network string, addr string) (net.Conn, error) {
343+
return dialOpenSSL(conf, network, addr)
344+
},
298345
}
299346

300347
client.Transport = &transport

client/client_auth_test.go

Lines changed: 158 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ import (
1919
"fmt"
2020
"io/ioutil"
2121
"net/http"
22-
"net/http/httptest"
2322
"testing"
2423

24+
"github.com/mendersoftware/mender/client/tests_helpers"
2525
"github.com/stretchr/testify/assert"
2626
)
2727

@@ -88,13 +88,15 @@ func TestClientAuth(t *testing.T) {
8888
http.Header{},
8989
}
9090

91-
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
91+
ts := startTestHTTPS(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
9292
responder.headers = r.Header
9393
w.WriteHeader(responder.httpStatus)
9494
w.Header().Set("Content-Type", "application/json")
9595

9696
fmt.Fprint(w, responder.data)
97-
}))
97+
}),
98+
localhostCert,
99+
localhostKey)
98100
defer ts.Close()
99101

100102
ac, err := NewApiClient(
@@ -122,8 +124,10 @@ func TestClientAuth(t *testing.T) {
122124
}
123125

124126
func TestClientAuthExpiredCert(t *testing.T) {
125-
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
126-
}))
127+
ts := startTestHTTPS(
128+
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}),
129+
localhostCertExpired,
130+
localhostKeyExpired)
127131
defer ts.Close()
128132

129133
ac, err := NewApiClient(
@@ -145,8 +149,10 @@ func TestClientAuthExpiredCert(t *testing.T) {
145149
}
146150

147151
func TestClientAuthUnknownAuthorityCert(t *testing.T) {
148-
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
149-
}))
152+
ts := startTestHTTPS(
153+
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}),
154+
localhostCertUnknown,
155+
localhostKeyUnknown)
150156
defer ts.Close()
151157

152158
ac, err := NewApiClient(
@@ -164,12 +170,76 @@ func TestClientAuthUnknownAuthorityCert(t *testing.T) {
164170
rsp, err := client.Request(ac, ts.URL, msger)
165171
assert.Error(t, err)
166172
assert.Contains(t, err.Error(), "certificate signed by unknown authority")
173+
// see https://github.com/openssl/openssl/blob/OpenSSL_1_1_1-stable/crypto/x509/x509_vfy.c#L3268
174+
// for self-signed openssl always returns self-signed error; either
175+
// X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT or X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN
176+
// and never X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT or X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY
177+
assert.Nil(t, rsp)
178+
}
179+
180+
//X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT
181+
func TestClientAuthDepthZeroSelfSignedCert(t *testing.T) {
182+
if tests_helpers.OpenSSLSecurityLevel < 2 {
183+
t.Skip("skipping TestClientAuthEndEntityKeyTooSmall - security level < 2")
184+
}
185+
ts := startTestHTTPS(
186+
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}),
187+
localhostCert,
188+
localhostKey)
189+
defer ts.Close()
190+
191+
ac, err := NewApiClient(
192+
Config{"server.zero.depth.self.signed.crt", true, false},
193+
)
194+
assert.NotNil(t, ac)
195+
assert.NoError(t, err)
196+
197+
client := NewAuth()
198+
assert.NotNil(t, client)
199+
200+
msger := &testAuthDataMessenger{
201+
reqData: []byte("foobar"),
202+
}
203+
rsp, err := client.Request(ac, ts.URL, msger)
204+
assert.Error(t, err)
205+
assert.Contains(t, err.Error(), "depth zero self-signed certificate")
206+
assert.Nil(t, rsp)
207+
}
208+
209+
//X509_V_ERR_EE_KEY_TOO_SMALL
210+
func TestClientAuthEndEntityKeyTooSmall(t *testing.T) {
211+
if tests_helpers.OpenSSLSecurityLevel < 2 {
212+
t.Skip("skipping TestClientAuthEndEntityKeyTooSmall - security level < 2")
213+
}
214+
ts := startTestHTTPS(
215+
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}),
216+
localhostCertShortEEKey,
217+
localhostKeyShortEEKey)
218+
defer ts.Close()
219+
220+
ac, err := NewApiClient(
221+
Config{"server.crt", true, false},
222+
)
223+
assert.NotNil(t, ac)
224+
assert.NoError(t, err)
225+
226+
client := NewAuth()
227+
assert.NotNil(t, client)
228+
229+
msger := &testAuthDataMessenger{
230+
reqData: []byte("foobar"),
231+
}
232+
rsp, err := client.Request(ac, ts.URL, msger)
233+
assert.Error(t, err)
234+
assert.Contains(t, err.Error(), "end entity key too short")
167235
assert.Nil(t, rsp)
168236
}
169237

170238
func TestClientAuthNoCert(t *testing.T) {
171-
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
172-
}))
239+
ts := startTestHTTPS(
240+
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}),
241+
localhostCert,
242+
localhostKey)
173243
defer ts.Close()
174244

175245
ac, err := NewApiClient(
@@ -178,3 +248,82 @@ func TestClientAuthNoCert(t *testing.T) {
178248
assert.NotNil(t, ac)
179249
assert.NoError(t, err)
180250
}
251+
252+
func TestClientAuthHostValidationNocheck(t *testing.T) {
253+
ts := startTestHTTPS(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}),
254+
localhostCert,
255+
localhostKey)
256+
defer ts.Close()
257+
258+
ac, err := NewApiClient(
259+
Config{"server.crt", true, true},
260+
)
261+
assert.NotNil(t, ac)
262+
assert.NoError(t, err)
263+
264+
client := NewAuth()
265+
assert.NotNil(t, client)
266+
267+
msger := &testAuthDataMessenger{
268+
reqData: []byte("foobar"),
269+
}
270+
rsp, err := client.Request(ac, ts.URL, msger)
271+
272+
assert.NoError(t, err)
273+
assert.NotNil(t, rsp)
274+
}
275+
276+
func TestClientAuthHostValidationError(t *testing.T) {
277+
ts := startTestHTTPS(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}),
278+
localhostCertWrongHost,
279+
localhostKeyWrongHost)
280+
defer ts.Close()
281+
282+
ac, err := NewApiClient(
283+
Config{"server.unknown-authority.crt", true, false},
284+
)
285+
assert.NotNil(t, ac)
286+
assert.NoError(t, err)
287+
288+
client := NewAuth()
289+
assert.NotNil(t, client)
290+
291+
msger := &testAuthDataMessenger{
292+
reqData: []byte("foobar"),
293+
}
294+
rsp, err := client.Request(ac, ts.URL, msger)
295+
296+
assert.Error(t, err)
297+
assert.Contains(t, err.Error(), "Host validation error")
298+
assert.Nil(t, rsp)
299+
}
300+
301+
// this tests for the error that is handled by default 'not a valid certificate'
302+
// via X509_V_ERR_CA_KEY_TOO_SMALL
303+
func TestClientAuthNotValidCertificate(t *testing.T) {
304+
if tests_helpers.OpenSSLSecurityLevel < 2 {
305+
t.Skip("skipping TestClientAuthEndEntityKeyTooSmall - security level < 2")
306+
}
307+
ts := startTestHTTPS(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}),
308+
localhostCertCAKeyTooShort,
309+
localhostKeyCAKeyTooShort)
310+
defer ts.Close()
311+
312+
ac, err := NewApiClient(
313+
Config{"server.ca.key.too.small.crt", true, false},
314+
)
315+
assert.NotNil(t, ac)
316+
assert.NoError(t, err)
317+
318+
client := NewAuth()
319+
assert.NotNil(t, client)
320+
321+
msger := &testAuthDataMessenger{
322+
reqData: []byte("foobar"),
323+
}
324+
rsp, err := client.Request(ac, ts.URL, msger)
325+
326+
assert.Error(t, err)
327+
assert.Contains(t, err.Error(), "not a valid certificate")
328+
assert.Nil(t, rsp)
329+
}

client/client_inventory_test.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2017 Northern.tech AS
1+
// Copyright 2020 Northern.tech AS
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -16,7 +16,6 @@ package client
1616
import (
1717
"io/ioutil"
1818
"net/http"
19-
"net/http/httptest"
2019
"testing"
2120

2221
"github.com/pkg/errors"
@@ -35,12 +34,15 @@ func TestInventoryClient(t *testing.T) {
3534
}
3635

3736
// Test server that always responds with 200 code, and specific payload
38-
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
39-
w.WriteHeader(responder.httpStatus)
37+
ts := startTestHTTPS(
38+
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
39+
w.WriteHeader(responder.httpStatus)
4040

41-
responder.recdata, _ = ioutil.ReadAll(r.Body)
42-
responder.path = r.URL.Path
43-
}))
41+
responder.recdata, _ = ioutil.ReadAll(r.Body)
42+
responder.path = r.URL.Path
43+
}),
44+
localhostCert,
45+
localhostKey)
4446
defer ts.Close()
4547

4648
ac, err := NewApiClient(

client/client_log_test.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2017 Northern.tech AS
1+
// Copyright 2020 Northern.tech AS
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -16,7 +16,6 @@ package client
1616
import (
1717
"io/ioutil"
1818
"net/http"
19-
"net/http/httptest"
2019
"testing"
2120

2221
"github.com/pkg/errors"
@@ -35,12 +34,15 @@ func TestLogUploadClient(t *testing.T) {
3534
}
3635

3736
// Test server that always responds with 200 code, and specific payload
38-
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
39-
w.WriteHeader(responder.httpStatus)
37+
ts := startTestHTTPS(
38+
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
39+
w.WriteHeader(responder.httpStatus)
4040

41-
responder.recdata, _ = ioutil.ReadAll(r.Body)
42-
responder.path = r.URL.Path
43-
}))
41+
responder.recdata, _ = ioutil.ReadAll(r.Body)
42+
responder.path = r.URL.Path
43+
}),
44+
localhostCert,
45+
localhostKey)
4446
defer ts.Close()
4547

4648
ac, err := NewApiClient(

client/client_status_test.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2018 Northern.tech AS
1+
// Copyright 2020 Northern.tech AS
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -16,7 +16,6 @@ package client
1616
import (
1717
"io/ioutil"
1818
"net/http"
19-
"net/http/httptest"
2019
"testing"
2120

2221
"github.com/pkg/errors"
@@ -35,12 +34,15 @@ func TestStatusClient(t *testing.T) {
3534
}
3635

3736
// Test server that always responds with 200 code, and specific payload
38-
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
39-
w.WriteHeader(responder.httpStatus)
37+
ts := startTestHTTPS(
38+
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
39+
w.WriteHeader(responder.httpStatus)
4040

41-
responder.recdata, _ = ioutil.ReadAll(r.Body)
42-
responder.path = r.URL.Path
43-
}))
41+
responder.recdata, _ = ioutil.ReadAll(r.Body)
42+
responder.path = r.URL.Path
43+
}),
44+
localhostCert,
45+
localhostKey)
4446
defer ts.Close()
4547

4648
ac, err := NewApiClient(

0 commit comments

Comments
 (0)