Repository: cossacklabs/acra (language: python)

Issue:
[ISSUE] tls_ocsp_from_cert: ignore doesn't ignore database OCSP, undocumented behaviour
**Describe the bug**
> A clear and concise description of what the bug is.

The configuration value `tls_ocsp_from_cert: ignore` is not working correctly with the database connection.
To get the expected behaviour, I had to use an **undocumented** setting `tls_ocsp_database_from_cert: ignore` which I guessed from [reading](https://github.com/cossacklabs/acra/blob/58d75484a9642aaac2d85d6dd88df4798e5763c0/network/cert_verifier.go#L61) the [code](https://github.com/cossacklabs/acra/blob/58d75484a9642aaac2d85d6dd88df4798e5763c0/network/tls_wrapper.go#L69), not the docs.

My expectation would be for it to be documented here: https://docs.cossacklabs.com/acra/configuring-maintaining/tls/ocsp/

I'm aware of the implications of switching off all these TLS settings and using the same certificate & key for client & server - honestly, I am just trying to get it to work with Python and the `asyncpg` library, with Cockroach Labs serverless DB which seems to not have OCSP set up as Acra expects it to be (separate bug? I don't know yet.)

**To Reproduce**
My YAML configuration (`db_host` is censored):
```yaml
# acra.yml - both encryptor and server config in one file because they don't seem to step on each other.
version: 0.94.0

schemas:
  - table: tbl_auditlog
    columns:
      - dc_entry_id
      - dc_guild
      - stamp
    encrypted:
      - column: dc_user
        searchable: true
      - column: dc_target
        searchable: true
      - column: py_auditlogentry

  - table: t_bot_settings
    columns:
      - ky
    encrypted:
      - column: dc_user
        searchable: true
      - column: json_val

postgresql_enable: true
db_host: "example.cluster.cockroachlabs.cloud"
db_port: 26257

keystore_cache_size: -1
keystore_cache_on_start_enable: false
tls_key: "./keys/opensslKey.key"
tls_cert: "./keys/opensslCertificate.crt"
tls_auth: 0
tls_client_id_from_cert: false
tls_ocsp_required: allowUnknown
tls_ocsp_from_cert: ignore
#tls_ocsp_database_from_cert: ignore
tls_crl_from_cert: ignore
tls_crl_database_from_cert: ignore
#tls_ocsp_url: ""
#tls_ocsp_client_url: ""
#tls_ocsp_database_url: "http://r3.o.lencr.org/ocsp"
```

My acra-server command:
`acra-server --config_file=./acra.yml --client_id=dev_acra_client --encryptor_config_storage_type=filesystem --encryptor_config_file=./acra.yml -v -d`

My test client (Python):
```py
import asyncio
import asyncpg
import ssl
sslctx1 = ssl.create_default_context(
    ssl.Purpose.SERVER_AUTH,
    cafile="./keys/opensslCertificate.crt"
)
sslctx1.load_cert_chain(
    "./keys/opensslCertificate.crt",
    keyfile="./keys/opensslKey.crt"
)
sslctx1.check_hostname = False
sslctx1.verify_mode = ssl.CERT_NONE


async def maintest(q):
    c = await asyncpg.connect(
                host="0.0.0.0", port=9393, user='user',
                password='qvr Trqnaxra fvaq serv',
                database="dev",
                ssl=sslctx1
    )
    x = await c.fetch(q)
    print(x)
    return x

asyncio.run(maintest("select * from pg_catalog.pg_user;"))
```

**Expected behavior**
> A clear and concise description of what you expected to happen.

Acra should ignore OCSP URLs on the certificates, on both connections.
The problem behaviour only happens when commenting out the `tls_ocsp_database_from_cert: ignore` line in the acra.yml file and then restarting the Acra server. The expected behaviour happens when it is uncommented.

**Acra configuration files**
- For AcraServer: 
    - [x] configuration file or CLI params that you use to start AcraServer; 
    - [x] `encryptor_config.yaml` if used.

**Environment (please complete the following information):**
- Acra version: [0.94.0+bullseye on Debian installed with the instructions in the docs]
- Database server and its version: [Cockroach DB Serverless, whatever the current version is (Postgres based)]
- Installed components:
   - [x] AcraServer
   - [ ] AcraTranslator
- Data-in-transit encryption between Acra and the client-side application:
   - [x] TLS
   - [ ] AcraConnector
   - [ ] no transport encryption
- Installation way:
   - [ ] via Docker
   - [x] via package manager


**Additional context**
> Add any other context about the problem here.

Failure debug logs will be attached.


Discussion / hints:
📁 [acra-issue-615.log](https://github.com/cossacklabs/acra/files/10283916/acra-issue-615.log)

And here are the logs from running it with this configuration (commented out: `#tls_ocsp_database_from_cert: ignore`)

The problem here is, it works normally once `tls_ocsp_database_from_cert: ignore` is set (uncommented).
I believe it should work normally just because `tls_ocsp_from_cert: ignore` is set because that is the documented behaviour.

Lines such as this, do not appear when the setting is uncommented (problem behaviour doesn't happen):
`time="2022-12-22T05:09:24Z" level=debug msg="OCSP: appending server http://r3.o.lencr.org, from cert"`
In fact, no mention of OCSP appears in the log in this case.
You did a great job, thank you. You are right, flags `tls_[ocsp|crl]_from_cert` doesn't override default values of `tls_[ocsp|crl]_[client|database]_from_cert`, and Acra expects explicit overriding values for both parameters.
And you are right, it is unexpected and not documented behavior.  We can update documentation and note that users should set empty values for parameters that have a non-empty default value, or we can set an empty default values (what we didn't do due to following our primary approach of secure by default configuration and using the strictest options by default), or we can update logic of parsing configuration options and ignore default values of not-set parameters and use general one if it set.

And we will choose the last option because it simplifies configuration by specifying only one parameter for all related groups of parameters. We will notify you and close this issue after the fix. 
Also, thank you for the feedback about not complete documentation about OCSP/CRL related configuration. We will update the documentation too.

Fix patch:
diff --git a/CHANGELOG_DEV.md b/CHANGELOG_DEV.md
index 160c90297..4995a870b 100644
--- a/CHANGELOG_DEV.md
+++ b/CHANGELOG_DEV.md
@@ -1,3 +1,6 @@
+# 0.94.5 - 2023-01-11
+- Improved TLS configuration, `tls_ocsp_*` and `tls_crl_*` applied for TLS connections without `tls_ocsp_[client|database]_*`/`tls_crl_[client|database]_*` flags.g
+
 # 0.94.0 - 2022-10-18
 - Extend SQL syntax for searchable encryption with support JOINs query. 
 
diff --git a/cmd/acra-server/acra-server.go b/cmd/acra-server/acra-server.go
index 43d7a497b..d23b2793e 100644
--- a/cmd/acra-server/acra-server.go
+++ b/cmd/acra-server/acra-server.go
@@ -143,7 +143,7 @@ func realMain() error {
 	enableHTTPAPI := flag.Bool("http_api_enable", false, "Enable HTTP API. Use together with --http_api_tls_transport_enable whenever possible.")
 	httpAPIUseTLS := flag.Bool("http_api_tls_transport_enable", false, "Enable HTTPS support for the API. Use together with the --http_api_enable. TLS configuration is the same as in the Acra Proxy.")
 
-	network.RegisterTLSBaseArgs()
+	network.RegisterTLSBaseArgs(flag.CommandLine)
 	network.RegisterTLSArgsForService(flag.CommandLine, false, "", network.ClientNameConstructorFunc())
 	network.RegisterTLSArgsForService(flag.CommandLine, true, "", network.DatabaseNameConstructorFunc())
 	tlsUseClientIDFromCertificate := flag.Bool("tls_client_id_from_cert", true, "Extract clientID from TLS certificate from application connection. Can't be used with --tls_client_auth=0 or --tls_auth=0")
diff --git a/cmd/acra-translator/acra-translator.go b/cmd/acra-translator/acra-translator.go
index d3f5727b2..1dc9e0027 100644
--- a/cmd/acra-translator/acra-translator.go
+++ b/cmd/acra-translator/acra-translator.go
@@ -137,7 +137,7 @@ func realMain() error {
 	cmd.RegisterTracingCmdParameters()
 	cmd.RegisterJaegerCmdParameters()
 	logging.RegisterCLIArgs()
-	network.RegisterTLSBaseArgs()
+	network.RegisterTLSBaseArgs(flag.CommandLine)
 
 	verbose := flag.Bool("v", false, "Log to stderr all INFO, WARNING and ERROR logs")
 	debug := flag.Bool("d", false, "Log everything to stderr")
diff --git a/network/cert_verifier.go b/network/cert_verifier.go
index 050d0b9c4..ca79b1364 100644
--- a/network/cert_verifier.go
+++ b/network/cert_verifier.go
@@ -68,21 +68,21 @@ func RegisterCertVerifierArgsForService(flags *flag.FlagSet, serviceName string,
 }
 
 // RegisterCertVerifierArgs register CLI args which allow to get CertVerifier by NewCertVerifier()
-func RegisterCertVerifierArgs() {
-	flag.StringVar(&tlsOcspURL, "tls_ocsp_url", "", "OCSP service URL")
-	flag.StringVar(&tlsOcspRequired, "tls_ocsp_required", OcspRequiredDenyUnknownStr,
+func RegisterCertVerifierArgs(flags *flag.FlagSet) {
+	flags.StringVar(&tlsOcspURL, "tls_ocsp_url", "", "OCSP service URL")
+	flags.StringVar(&tlsOcspRequired, "tls_ocsp_required", OcspRequiredDenyUnknownStr,
 		fmt.Sprintf("How to treat certificates unknown to OCSP: <%s>", strings.Join(OcspRequiredValuesList, "|")))
-	flag.StringVar(&tlsOcspFromCert, "tls_ocsp_from_cert", OcspFromCertPreferStr,
+	flags.StringVar(&tlsOcspFromCert, "tls_ocsp_from_cert", OcspFromCertPreferStr,
 		fmt.Sprintf("How to treat OCSP server described in certificate itself: <%s>", strings.Join(OcspFromCertValuesList, "|")))
-	flag.BoolVar(&tlsOcspCheckOnlyLeafCertificate, "tls_ocsp_check_only_leaf_certificate", false,
+	flags.BoolVar(&tlsOcspCheckOnlyLeafCertificate, "tls_ocsp_check_only_leaf_certificate", false,
 		"Put 'true' to check only final/last certificate, or 'false' to check the whole certificate chain using OCSP")
-	flag.StringVar(&tlsCrlURL, "tls_crl_url", "", "URL of the Certificate Revocation List (CRL) to use")
-	flag.StringVar(&tlsCrlFromCert, "tls_crl_from_cert", CrlFromCertPreferStr,
+	flags.StringVar(&tlsCrlURL, "tls_crl_url", "", "URL of the Certificate Revocation List (CRL) to use")
+	flags.StringVar(&tlsCrlFromCert, "tls_crl_from_cert", CrlFromCertPreferStr,
 		fmt.Sprintf("How to treat CRL URL described in certificate itself: <%s>", strings.Join(CrlFromCertValuesList, "|")))
-	flag.BoolVar(&tlsCrlCheckOnlyLeafCertificate, "tls_crl_check_only_leaf_certificate", false,
+	flags.BoolVar(&tlsCrlCheckOnlyLeafCertificate, "tls_crl_check_only_leaf_certificate", false,
 		"Put 'true' to check only final/last certificate, or 'false' to check the whole certificate chain using CRL")
-	flag.UintVar(&tlsCrlCacheSize, "tls_crl_cache_size", CrlDefaultCacheSize, "How many CRLs to cache in memory (use 0 to disable caching)")
-	flag.UintVar(&tlsCrlCacheTime, "tls_crl_cache_time", CrlDisableCacheTime,
+	flags.UintVar(&tlsCrlCacheSize, "tls_crl_cache_size", CrlDefaultCacheSize, "How many CRLs to cache in memory (use 0 to disable caching)")
+	flags.UintVar(&tlsCrlCacheTime, "tls_crl_cache_time", CrlDisableCacheTime,
 		fmt.Sprintf("How long to keep CRLs cached, in seconds (use 0 to disable caching, maximum: %d s)", CrlCacheTimeMax))
 }
 
diff --git a/network/crl.go b/network/crl.go
index 702c7df14..03ed35248 100644
--- a/network/crl.go
+++ b/network/crl.go
@@ -134,33 +134,43 @@ func NewCRLConfigByName(flags *flag.FlagSet, name string, namerFunc CLIParamName
 	var crlURL, crlFromCert string
 	var crlCheckOnlyLeafCertificate bool
 	var crlCacheSize, crlCacheTime uint
-	if f := flags.Lookup(namerFunc(name, "url", "crl")); f != nil {
+
+	crlURL = tlsCrlURL
+	if isFlagSet(namerFunc(name, "url", "crl"), flags) {
+		f := flags.Lookup(namerFunc(name, "url", "crl"))
 		crlURL = f.Value.String()
-		if crlURL == "" {
-			crlURL = tlsCrlURL
-		}
 	}
-	if f := flags.Lookup(namerFunc(name, "from_cert", "crl")); f != nil {
+
+	crlFromCert = tlsCrlFromCert
+	if isFlagSet(namerFunc(name, "from_cert", "crl"), flags) {
+		f := flags.Lookup(namerFunc(name, "from_cert", "crl"))
 		crlFromCert = f.Value.String()
-		if crlFromCert == "" {
-			crlFromCert = tlsCrlFromCert
-		}
 	}
-	if f := flags.Lookup(namerFunc(name, "check_only_leaf_certificate", "crl")); f != nil {
+
+	// use common flag if named option not set
+	crlCheckOnlyLeafCertificate = tlsCrlCheckOnlyLeafCertificate
+	if isFlagSet(namerFunc(name, "check_only_leaf_certificate", "crl"), flags) {
+		f := flags.Lookup(namerFunc(name, "check_only_leaf_certificate", "crl"))
 		v, err := strconv.ParseBool(f.Value.String())
 		if err != nil {
 			log.WithField("value", f.Value.String).Fatalf("Can't cast %s to boolean value", namerFunc(name, "check_only_leaf_certificate", "crl"))
 		}
 		crlCheckOnlyLeafCertificate = v
 	}
-	if f := flags.Lookup(namerFunc(name, "cache_size", "crl")); f != nil {
+
+	crlCacheSize = tlsCrlCacheSize
+	if isFlagSet(namerFunc(name, "cache_size", "crl"), flags) {
+		f := flags.Lookup(namerFunc(name, "cache_size", "crl"))
 		size, err := strconv.ParseUint(f.Value.String(), 10, 64)
 		if err != nil {
 			log.WithField("value", f.Value.String).Fatalf("Can't cast %s to integer value", namerFunc(name, "cache_size", "crl"))
 		}
 		crlCacheSize = uint(size)
 	}
-	if f := flags.Lookup(namerFunc(name, "cache_time", "crl")); f != nil {
+
+	crlCacheTime = tlsCrlCacheTime
+	if isFlagSet(namerFunc(name, "cache_time", "crl"), flags) {
+		f := flags.Lookup(namerFunc(name, "cache_time", "crl"))
 		cacheTime, err := strconv.ParseUint(f.Value.String(), 10, 64)
 		if err != nil {
 			log.WithField("value", f.Value.String).Fatalf("Can't cast %s to integer value", namerFunc(name, "cache_time", "crl"))
diff --git a/network/ocsp.go b/network/ocsp.go
index 3191847db..3fbb5cd12 100644
--- a/network/ocsp.go
+++ b/network/ocsp.go
@@ -130,31 +130,35 @@ const (
 func NewOCSPConfigByName(flags *flag.FlagSet, name string, namerFunc CLIParamNameConstructorFunc) (*OCSPConfig, error) {
 	var url, required, fromCert string
 	var checkOnlyLeafCert bool
-	if f := flags.Lookup(namerFunc(name, "url", "ocsp")); f != nil {
+
+	url = tlsOcspURL
+	if isFlagSet(namerFunc(name, "url", "ocsp"), flags) {
+		f := flags.Lookup(namerFunc(name, "url", "ocsp"))
 		url = f.Value.String()
-		if url == "" {
-			url = tlsOcspURL
-		}
 	}
-	if f := flags.Lookup(namerFunc(name, "required", "ocsp")); f != nil {
+
+	required = tlsOcspRequired
+	if isFlagSet(namerFunc(name, "required", "ocsp"), flags) {
+		f := flags.Lookup(namerFunc(name, "required", "ocsp"))
 		required = f.Value.String()
-		if required == "" {
-			required = tlsOcspRequired
-		}
 	}
-	if f := flags.Lookup(namerFunc(name, "from_cert", "ocsp")); f != nil {
+
+	fromCert = tlsOcspFromCert
+	if isFlagSet(namerFunc(name, "from_cert", "ocsp"), flags) {
+		f := flags.Lookup(namerFunc(name, "from_cert", "ocsp"))
 		fromCert = f.Value.String()
-		if fromCert == "" {
-			fromCert = tlsOcspFromCert
-		}
 	}
-	if f := flags.Lookup(namerFunc(name, "check_only_leaf_certificate", "ocsp")); f != nil {
+
+	checkOnlyLeafCert = tlsOcspCheckOnlyLeafCertificate
+	if isFlagSet(namerFunc(name, "check_only_leaf_certificate", "ocsp"), flags) {
+		f := flags.Lookup(namerFunc(name, "check_only_leaf_certificate", "ocsp"))
 		v, err := strconv.ParseBool(f.Value.String())
 		if err != nil {
 			log.WithField("value", f.Value.String).Fatalf("Can't cast %s to boolean value", namerFunc(name, "check_only_leaf_certificate", "ocsp"))
 		}
 		checkOnlyLeafCert = v
 	}
+
 	return NewOCSPConfig(url, required, fromCert, checkOnlyLeafCert)
 }
 
diff --git a/network/tls_wrapper.go b/network/tls_wrapper.go
index 910ee4474..d89a4807e 100644
--- a/network/tls_wrapper.go
+++ b/network/tls_wrapper.go
@@ -102,12 +102,12 @@ func DatabaseNameConstructorFunc() CLIParamNameConstructorFunc {
 }
 
 // RegisterTLSBaseArgs register CLI args tls_ca|tls_key|tls_cert|tls_auth which allow to get tls.Config by NewTLSConfigFromBaseArgs function
-func RegisterTLSBaseArgs() {
-	flag.StringVar(&tlsCA, "tls_ca", "", "Path to root certificate which will be used with system root certificates to validate peer's certificate")
-	flag.StringVar(&tlsKey, "tls_key", "", "Path to private key that will be used for TLS connections")
-	flag.StringVar(&tlsCert, "tls_cert", "", "Path to certificate")
-	flag.IntVar(&tlsAuthType, "tls_auth", int(tls.RequireAndVerifyClientCert), "Set authentication mode that will be used in TLS connection. Values in range 0-4 that set auth type (https://golang.org/pkg/crypto/tls/#ClientAuthType). Default is tls.RequireAndVerifyClientCert")
-	RegisterCertVerifierArgs()
+func RegisterTLSBaseArgs(flags *flag.FlagSet) {
+	flags.StringVar(&tlsCA, "tls_ca", "", "Path to root certificate which will be used with system root certificates to validate peer's certificate")
+	flags.StringVar(&tlsKey, "tls_key", "", "Path to private key that will be used for TLS connections")
+	flags.StringVar(&tlsCert, "tls_cert", "", "Path to certificate")
+	flags.IntVar(&tlsAuthType, "tls_auth", int(tls.RequireAndVerifyClientCert), "Set authentication mode that will be used in TLS connection. Values in range 0-4 that set auth type (https://golang.org/pkg/crypto/tls/#ClientAuthType). Default is tls.RequireAndVerifyClientCert")
+	RegisterCertVerifierArgs(flags)
 }
 
 // RegisterTLSArgsForService register CLI args tls_ca|tls_key|tls_cert|tls_auth and flags for certificate verifier
diff --git a/network/utils.go b/network/utils.go
index cebda579a..e2ea08392 100644
--- a/network/utils.go
+++ b/network/utils.go
@@ -17,6 +17,7 @@ limitations under the License.
 package network
 
 import (
+	"flag"
 	"fmt"
 	"net"
 	url_ "net/url"
@@ -229,3 +230,15 @@ func SplitConnectionString(connectionString string) (string, int, error) {
 	}
 	return url.Hostname(), port, nil
 }
+
+// IsFlagSet returns true if flag explicitly set via CLI arguments
+// Don't move it to the cmd package due to import cycle
+func isFlagSet(name string, flagset *flag.FlagSet) bool {
+	set := false
+	flagset.Visit(func(f *flag.Flag) {
+		if f.Name == name {
+			set = true
+		}
+	})
+	return set
+}


Test patch:
diff --git a/cmd/acra-keys/keys/read-keys_tls_redis_test.go b/cmd/acra-keys/keys/read-keys_tls_redis_test.go
index 487eda357..1c515d930 100644
--- a/cmd/acra-keys/keys/read-keys_tls_redis_test.go
+++ b/cmd/acra-keys/keys/read-keys_tls_redis_test.go
@@ -99,6 +99,7 @@ func TestReadCMD_TLSRedis_V2(t *testing.T) {
 	flagSet := flag.NewFlagSet(CmdMigrateKeys, flag.ContinueOnError)
 	keyloader.RegisterCLIParametersWithFlagSet(flagSet, "", "")
 	cmd.RegisterRedisKeystoreParametersWithPrefix(flagSet, "", "")
+	network.RegisterTLSBaseArgs(flagSet)
 
 	options := prepareTLSRedisConfigForFlagSet(flagSet, t)
 	// remove all generated keys at the end
@@ -110,6 +111,10 @@ func TestReadCMD_TLSRedis_V2(t *testing.T) {
 
 	setFlags := map[string]string{
 		"keystore_encryption_type": keyloader.KeystoreStrategyEnvMasterKey,
+		// test certs contains specified OCSP/CRL endpoints that force our TLS verifier to check it
+		// turn on ignoring it to avoid starting extra servers for ocsp/crl responses
+		"tls_ocsp_from_cert": network.OcspFromCertIgnoreStr,
+		"tls_crl_from_cert":  network.CrlFromCertIgnoreStr,
 	}
 
 	for flag, value := range setFlags {
@@ -175,7 +180,7 @@ func TestReadCMD_TLSRedis_V1(t *testing.T) {
 
 	flagSet := flag.NewFlagSet(CmdMigrateKeys, flag.ContinueOnError)
 	keyloader.RegisterCLIParametersWithFlagSet(flagSet, "", "")
-
+	network.RegisterTLSBaseArgs(flagSet)
 	cmd.RegisterRedisKeystoreParametersWithPrefix(flagSet, "", "")
 
 	options := prepareTLSRedisConfigForFlagSet(flagSet, t)
@@ -188,6 +193,10 @@ func TestReadCMD_TLSRedis_V1(t *testing.T) {
 
 	setFlags := map[string]string{
 		"keystore_encryption_type": keyloader.KeystoreStrategyEnvMasterKey,
+		// test certs contains specified OCSP/CRL endpoints that force our TLS verifier to check it
+		// turn on ignoring it to avoid starting extra servers for ocsp/crl responses
+		"tls_ocsp_from_cert": network.OcspFromCertIgnoreStr,
+		"tls_crl_from_cert":  network.CrlFromCertIgnoreStr,
 	}
 
 	for flag, value := range setFlags {
diff --git a/keystore/v2/keystore/filesystem/backend/redis_tls_test.go b/keystore/v2/keystore/filesystem/backend/redis_tls_test.go
index bd0b98880..322d4397b 100644
--- a/keystore/v2/keystore/filesystem/backend/redis_tls_test.go
+++ b/keystore/v2/keystore/filesystem/backend/redis_tls_test.go
@@ -23,6 +23,7 @@ import (
 	"crypto/tls"
 	"flag"
 	"github.com/cossacklabs/acra/cmd"
+	"github.com/cossacklabs/acra/network"
 	tests2 "github.com/cossacklabs/acra/utils/tests"
 	"path/filepath"
 	"strconv"
@@ -87,6 +88,20 @@ func prepareTLSRedisConfig(t *testing.T) (*cmd.RedisOptions, *flag.FlagSet) {
 
 func TestRedis(t *testing.T) {
 	options, flagset := prepareTLSRedisConfig(t)
+	network.RegisterTLSBaseArgs(flagset)
+	setFlags := map[string]string{
+		// test certs contains specified OCSP/CRL endpoints that force our TLS verifier to check it
+		// turn on ignoring it to avoid starting extra servers for ocsp/crl responses
+		"tls_ocsp_from_cert": network.OcspFromCertIgnoreStr,
+		"tls_crl_from_cert":  network.CrlFromCertIgnoreStr,
+	}
+
+	for flag, value := range setFlags {
+		err := flagset.Set(flag, value)
+		if err != nil {
+			t.Fatal(err)
+		}
+	}
 	redisOptions, err := options.KeysOptions(flagset)
 	if err != nil {
 		t.Fatal(err)
diff --git a/network/crl_test.go b/network/crl_test.go
index 107f9e682..9a6a6f844 100644
--- a/network/crl_test.go
+++ b/network/crl_test.go
@@ -28,6 +28,7 @@ import (
 	"net/http"
 	"os"
 	"path"
+	"strconv"
 	"testing"
 	"time"
 )
@@ -56,6 +57,9 @@ const (
 
 	// CRL that contains self-revoked root CA
 	TestCRLFilenameRootSelfRevoked = "ca/crl_with_root.pem"
+
+	// special port value force kernel to choose free port
+	freePort = 0
 )
 
 type TestCertGroup struct {
@@ -198,8 +202,8 @@ func pemToX509Cert(data []byte) (*x509.Certificate, error) {
 
 // Starts HTTP server on some random port, uses `mux` to handle requests,
 // returns created server and IP:port of listening socket
-func getTestHTTPServer(t *testing.T, mux *http.ServeMux) (*http.Server, string) {
-	listener, err := net.Listen("tcp", "127.0.0.1:0")
+func getTestHTTPServer(t *testing.T, port int, mux *http.ServeMux) (*http.Server, string) {
+	listener, err := net.Listen("tcp", "127.0.0.1:"+strconv.Itoa(port))
 	if err != nil {
 		t.Fatalf("Cannot create TCP socket for HTTP server: %v", err)
 	}
@@ -356,7 +360,7 @@ func TestDefaultCRLClientHTTP(t *testing.T) {
 		res.Write(rawCRL)
 	})
 
-	httpServer, addr := getTestHTTPServer(t, mux)
+	httpServer, addr := getTestHTTPServer(t, freePort, mux)
 	defer httpServer.Close()
 
 	crlClient := NewDefaultCRLClient()
@@ -479,7 +483,7 @@ func testDefaultCRLVerifierWithGroupValid(t *testing.T, certGroup TestCertGroup)
 		res.Write(rawCRL)
 	})
 
-	httpServer, addr := getTestHTTPServer(t, mux)
+	httpServer, addr := getTestHTTPServer(t, freePort, mux)
 	defer httpServer.Close()
 
 	// Test with valid URL
@@ -513,7 +517,7 @@ func testDefaultCRLVerifierWithGroupRevoked(t *testing.T, certGroup TestCertGrou
 		res.Write(rawCRL)
 	})
 
-	httpServer, addr := getTestHTTPServer(t, mux)
+	httpServer, addr := getTestHTTPServer(t, freePort, mux)
 	defer httpServer.Close()
 
 	// Test with valid URL
diff --git a/network/ocsp_test.go b/network/ocsp_test.go
index 2fc1fc503..783ff5d35 100644
--- a/network/ocsp_test.go
+++ b/network/ocsp_test.go
@@ -88,7 +88,7 @@ type ocspServerConfig struct {
 	respondWithWrongSerial bool
 }
 
-func getTestOCSPServer(t *testing.T, config ocspServerConfig) (*http.Server, string) {
+func getTestOCSPServer(t *testing.T, config ocspServerConfig, port int) (*http.Server, string) {
 	mux := http.NewServeMux()
 	mux.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) {
 		contentType, ok := req.Header["Content-Type"]
@@ -167,7 +167,7 @@ func getTestOCSPServer(t *testing.T, config ocspServerConfig) (*http.Server, str
 		// t.Logf("Wrote %d bytes OCSP response\n", n)
 	})
 
-	httpServer, addr := getTestHTTPServer(t, mux)
+	httpServer, addr := getTestHTTPServer(t, port, mux)
 
 	return httpServer, addr
 }
@@ -291,7 +291,7 @@ func testDefaultOCSPClientWithGroupValid(t *testing.T, certGroup TestCertGroup)
 		testCases:     []*ocspTestCase{goodData},
 	}
 
-	ocspServer, addr := getTestOCSPServer(t, ocspServerConfig)
+	ocspServer, addr := getTestOCSPServer(t, ocspServerConfig, freePort)
 	defer ocspServer.Close()
 
 	ocspClient := NewDefaultOCSPClient()
@@ -333,7 +333,7 @@ func testDefaultOCSPClientWithGroupRevoked(t *testing.T, certGroup TestCertGroup
 		testCases:     []*ocspTestCase{revokedData},
 	}
 
-	ocspServer, addr := getTestOCSPServer(t, ocspServerConfig)
+	ocspServer, addr := getTestOCSPServer(t, ocspServerConfig, freePort)
 	defer ocspServer.Close()
 
 	ocspClient := NewDefaultOCSPClient()
@@ -376,7 +376,7 @@ func testDefaultOCSPClientWrongResponse(t *testing.T, certGroup TestCertGroup) {
 		respondWithWrongSerial: true,
 	}
 
-	ocspServer, addr := getTestOCSPServer(t, ocspServerConfig)
+	ocspServer, addr := getTestOCSPServer(t, ocspServerConfig, freePort)
 	defer ocspServer.Close()
 
 	ocspClient := NewDefaultOCSPClient()
@@ -478,7 +478,7 @@ func testDefaultOCSPVerifierWithGroupValid(t *testing.T, certGroup TestCertGroup
 		testCases:     []*ocspTestCase{goodData},
 	}
 
-	ocspServer, addr := getTestOCSPServer(t, ocspServerConfig)
+	ocspServer, addr := getTestOCSPServer(t, ocspServerConfig, freePort)
 	defer ocspServer.Close()
 
 	url := fmt.Sprintf("http://%s", addr)
@@ -526,7 +526,7 @@ func testDefaultOCSPVerifierWithGroupRevoked(t *testing.T, certGroup TestCertGro
 		testCases:     []*ocspTestCase{revokedData},
 	}
 
-	ocspServer, addr := getTestOCSPServer(t, ocspServerConfig)
+	ocspServer, addr := getTestOCSPServer(t, ocspServerConfig, freePort)
 	defer ocspServer.Close()
 
 	url := fmt.Sprintf("http://%s", addr)
diff --git a/network/tls_wrapper_test.go b/network/tls_wrapper_test.go
index 36eba510b..8388d0476 100644
--- a/network/tls_wrapper_test.go
+++ b/network/tls_wrapper_test.go
@@ -7,12 +7,19 @@ import (
 	"crypto/x509"
 	"encoding/hex"
 	"errors"
+	"flag"
 	"fmt"
 	"github.com/cossacklabs/acra/network/testutils"
+	"github.com/cossacklabs/acra/utils/tests"
 	"github.com/stretchr/testify/assert"
+	"golang.org/x/crypto/ocsp"
 	"google.golang.org/grpc/credentials"
 	"net"
+	"net/http"
+	"path"
+	"reflect"
 	"runtime/debug"
+	"strconv"
 	"strings"
 	"sync"
 	"testing"
@@ -617,3 +624,221 @@ func TestClientAuth0WithClientIDExtraction(t *testing.T) {
 		t.Fatalf("Expect ErrInvalidTLSConfiguration, took %s\n", err.Error())
 	}
 }
+
+func TestNewTLSConfigByName(t *testing.T) {
+	// copy-pasted from ocsp_test.go
+	certGroup := getTestCertGroup(t)
+	goodData := &ocspTestCase{
+		cert:           certGroup.validVerifiedChains[0][0],
+		issuer:         getIssuerForTestChain(t, certGroup.validVerifiedChains),
+		expectedStatus: ocsp.Good,
+	}
+	ocspCertificate, ocspSigningKey := getTestOCSPCertAndKey(t, certGroup.prefix, certGroup.ocspCert, certGroup.ocspKey)
+	ocspServerConf := ocspServerConfig{
+		issuerCert:    goodData.issuer,
+		responderCert: ocspCertificate,
+		responderKey:  ocspSigningKey,
+		testCases:     []*ocspTestCase{goodData},
+	}
+
+	ocspServer, addr := getTestOCSPServer(t, ocspServerConf, freePort)
+	defer ocspServer.Close()
+	ocspURL := "http://" + addr
+
+	// copy-pasted from crl_test.go
+	var rawCRL []byte
+	if certGroup.validCRL != "" {
+		rawCRL, _ = getTestCRL(t, path.Join(certGroup.prefix, certGroup.validCRL))
+	} else {
+		rawCRL, _ = getTestCRL(t, path.Join(certGroup.prefix, certGroup.crl))
+	}
+	mux := http.NewServeMux()
+	crlEndpoint := "/test_crl.pem"
+	mux.HandleFunc(crlEndpoint, func(res http.ResponseWriter, req *http.Request) {
+		res.WriteHeader(200)
+		res.Header().Add("Content-Type", "application/pem-certificate-chain")
+		res.Write(rawCRL)
+	})
+
+	crlServer, addr := getTestHTTPServer(t, freePort, mux)
+	defer crlServer.Close()
+	crlURL := "http://" + addr + crlEndpoint
+
+	flagset := flag.FlagSet{}
+	RegisterTLSBaseArgs(&flagset)
+	RegisterTLSArgsForService(&flagset, true, "", ClientNameConstructorFunc())
+	type testcase struct {
+		baseCa           *string
+		baseCrt          *string
+		baseKey          *string
+		baseAuth         *int
+		baseOCSPUrl      *string
+		baseOCSPRequired *string
+		baseOCSPFromCert *string
+		baseCRLUrl       *string
+		baseCRLFromCert  *string
+
+		clientCa           *string
+		clientCrt          *string
+		clientKey          *string
+		clientAuth         *int
+		clientCRLUrl       *string
+		clientCRLFromCert  *string
+		clientOCSPUrl      *string
+		clientOCSPRequired *string
+		clientOCSPFromCert *string
+
+		expectedCa        string
+		expectedCrt       string
+		expectedKey       string
+		expectedAuth      int
+		verificationErr   error
+		expectedConfigErr error
+	}
+	rootPath := tests.GetSourceRootDirectory(t)
+	baseCa := rootPath + "/tests/ssl/ca/ca.crt"
+	baseCrt := rootPath + "/tests/ssl/acra-client/acra-client.crt"
+	baseKey := rootPath + "/tests/ssl/acra-client/acra-client.key"
+	baseAuth := 4
+	invalidAuth := 0
+	emptyStr := ""
+
+	invalidPath := "invalid"
+	getRefForConst := func(val string) *string {
+		return &val
+	}
+
+	testcases := []testcase{
+		// used configuration from base args
+		{
+			baseCa: &baseCa, baseKey: &baseKey, baseCrt: &baseCrt, baseAuth: &baseAuth, baseOCSPUrl: &ocspURL,
+			baseCRLUrl: &crlURL, baseCRLFromCert: getRefForConst(CrlFromCertIgnoreStr),
+			baseOCSPRequired: getRefForConst(OcspRequiredGoodStr), baseOCSPFromCert: getRefForConst(OcspFromCertIgnoreStr),
+
+			clientCa: nil, clientCrt: nil, clientKey: nil, clientAuth: nil, clientOCSPUrl: nil, clientCRLUrl: nil,
+			clientCRLFromCert: nil, clientOCSPRequired: nil, clientOCSPFromCert: nil,
+
+			expectedCa: baseCa, expectedCrt: baseCrt, expectedKey: baseKey, expectedAuth: baseAuth, verificationErr: nil},
+
+		// used invalid client's args with empty values
+		{
+			baseCa: &baseCa, baseKey: &baseKey, baseCrt: &baseCrt, baseAuth: &baseAuth, baseOCSPUrl: &ocspURL,
+			baseCRLUrl: &crlURL, baseCRLFromCert: getRefForConst(CrlFromCertIgnoreStr),
+			baseOCSPRequired: getRefForConst(OcspRequiredGoodStr), baseOCSPFromCert: getRefForConst(OcspFromCertIgnoreStr),
+
+			clientCa: &emptyStr, clientCrt: &emptyStr, clientKey: &emptyStr, clientAuth: &baseAuth,
+			clientOCSPUrl: &emptyStr, clientCRLUrl: &emptyStr,
+			clientCRLFromCert: &emptyStr, clientOCSPRequired: &emptyStr, clientOCSPFromCert: &emptyStr,
+
+			expectedCa: baseCa, expectedCrt: baseCrt, expectedKey: baseKey, expectedAuth: baseAuth, verificationErr: nil,
+			expectedConfigErr: ErrInvalidConfigOCSPRequired},
+
+		// used configuration from client args
+		{
+			baseCa: nil, baseCrt: nil, baseKey: nil, baseAuth: nil, baseOCSPUrl: nil, baseCRLUrl: nil,
+			baseCRLFromCert: nil, baseOCSPRequired: nil, baseOCSPFromCert: nil,
+
+			clientCa: &baseCa, clientKey: &baseKey, clientCrt: &baseCrt, clientAuth: &baseAuth, clientOCSPUrl: &ocspURL,
+			clientCRLUrl: &crlURL, clientCRLFromCert: getRefForConst(CrlFromCertIgnoreStr),
+			clientOCSPRequired: getRefForConst(OcspRequiredGoodStr), clientOCSPFromCert: getRefForConst(OcspFromCertIgnoreStr),
+
+			expectedCa: baseCa, expectedCrt: baseCrt, expectedKey: baseKey, expectedAuth: baseAuth, verificationErr: nil},
+
+		// client args override base args
+		{
+			baseCa: &invalidPath, baseCrt: &invalidPath, baseKey: &invalidPath, baseAuth: &invalidAuth, baseOCSPUrl: &invalidPath, baseCRLUrl: &invalidPath,
+			baseCRLFromCert: &invalidPath, baseOCSPRequired: &invalidPath, baseOCSPFromCert: &invalidPath,
+
+			clientCa: &baseCa, clientKey: &baseKey, clientCrt: &baseCrt, clientAuth: &baseAuth, clientOCSPUrl: &ocspURL,
+			clientCRLUrl: &crlURL, clientCRLFromCert: getRefForConst(CrlFromCertIgnoreStr),
+			clientOCSPRequired: getRefForConst(OcspRequiredGoodStr), clientOCSPFromCert: getRefForConst(OcspFromCertIgnoreStr),
+
+			expectedCa: baseCa, expectedCrt: baseCrt, expectedKey: baseKey, expectedAuth: baseAuth, verificationErr: nil},
+	}
+
+	generateArgs := func(tcase testcase) (out []string) {
+		if tcase.baseCa != nil {
+			out = append(out, "-tls_ca="+*tcase.baseCa)
+		}
+		if tcase.baseCrt != nil {
+			out = append(out, "-tls_cert="+*tcase.baseCrt)
+		}
+		if tcase.baseKey != nil {
+			out = append(out, "-tls_key="+*tcase.baseKey)
+		}
+		if tcase.baseAuth != nil {
+			out = append(out, "-tls_auth="+strconv.Itoa(*tcase.baseAuth))
+		}
+		if tcase.baseCRLFromCert != nil {
+			out = append(out, "-tls_crl_from_cert="+*tcase.baseCRLFromCert)
+		}
+		if tcase.baseOCSPUrl != nil {
+			out = append(out, "-tls_ocsp_url="+*tcase.baseOCSPUrl)
+		}
+		if tcase.baseOCSPRequired != nil {
+			out = append(out, "-tls_ocsp_required="+*tcase.baseOCSPRequired)
+		}
+		if tcase.baseOCSPFromCert != nil {
+			out = append(out, "-tls_ocsp_from_cert="+*tcase.baseOCSPFromCert)
+		}
+
+		if tcase.clientCa != nil {
+			out = append(out, "-tls_client_ca="+*tcase.clientCa)
+		}
+		if tcase.clientCrt != nil {
+			out = append(out, "-tls_client_cert="+*tcase.clientCrt)
+		}
+		if tcase.clientKey != nil {
+			out = append(out, "-tls_client_key="+*tcase.clientKey)
+		}
+		if tcase.clientAuth != nil {
+			out = append(out, "-tls_client_auth="+strconv.Itoa(*tcase.clientAuth))
+		}
+		if tcase.clientCRLFromCert != nil {
+			out = append(out, "-tls_crl_client_from_cert="+*tcase.clientCRLFromCert)
+		}
+		if tcase.clientOCSPUrl != nil {
+			out = append(out, "-tls_ocsp_client_url="+*tcase.clientOCSPUrl)
+		}
+		if tcase.clientOCSPRequired != nil {
+			out = append(out, "-tls_ocsp_client_required="+*tcase.clientOCSPRequired)
+		}
+		if tcase.clientOCSPFromCert != nil {
+			out = append(out, "-tls_ocsp_client_from_cert="+*tcase.clientOCSPFromCert)
+		}
+		return
+	}
+	for _, tcase := range testcases {
+		args := generateArgs(tcase)
+		if err := flagset.Parse(args); err != nil {
+			t.Fatal(err)
+		}
+		tlsConfig, err := NewTLSConfigByName(&flagset, "", "localhost", ClientNameConstructorFunc())
+		if err != tcase.expectedConfigErr {
+			t.Fatalf("Unexpected err value, took %v", err)
+		}
+		// next steps requires config
+		if err != nil {
+			continue
+		}
+
+		if len(tlsConfig.Certificates) != 1 {
+			t.Fatal("Incorrect amount of certificates")
+		}
+		expectedCert, err := tls.LoadX509KeyPair(tcase.expectedCrt, tcase.expectedKey)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !reflect.DeepEqual(expectedCert, tlsConfig.Certificates[0]) {
+			t.Fatal("Config's certificate not equal to the expected")
+		}
+
+		if int(tlsConfig.ClientAuth) != tcase.expectedAuth {
+			t.Fatal("Incorrect ClientAuth value")
+		}
+		err = tlsConfig.VerifyPeerCertificate(certGroup.validRawCerts, certGroup.validVerifiedChains)
+		if err != tcase.verificationErr {
+			t.Fatalf("Unexpected verification error, took %v", err)
+		}
+	}
+}