{
+ if (event.target.checked) {
+ change('private_key', '');
+ }
+ if (handleChange) {
+ handleChange(event);
+ }
+ }}
+ />,
+ ,
+ ]}
{(privateKey || privateKeyPath) && (
@@ -422,6 +441,7 @@ Form.propTypes = {
setTlsConfig: PropTypes.func.isRequired,
certificateSource: PropTypes.string,
privateKeySource: PropTypes.string,
+ privateKeySaved: PropTypes.bool,
};
const selector = formValueSelector(FORM_NAME.ENCRYPTION);
@@ -434,6 +454,7 @@ Form = connect((state) => {
const privateKeyPath = selector(state, 'private_key_path');
const certificateSource = selector(state, 'certificate_source');
const privateKeySource = selector(state, 'key_source');
+ const privateKeySaved = selector(state, 'private_key_saved');
return {
isEnabled,
certificateChain,
@@ -442,6 +463,7 @@ Form = connect((state) => {
privateKeyPath,
certificateSource,
privateKeySource,
+ privateKeySaved,
};
})(Form);
diff --git a/client/src/components/Settings/Encryption/index.js b/client/src/components/Settings/Encryption/index.js
index f7ca52e0..13f5c47c 100644
--- a/client/src/components/Settings/Encryption/index.js
+++ b/client/src/components/Settings/Encryption/index.js
@@ -29,9 +29,13 @@ class Encryption extends Component {
}, DEBOUNCE_TIMEOUT);
getInitialValues = (data) => {
- const { certificate_chain, private_key } = data;
- const certificate_source = certificate_chain ? 'content' : 'path';
- const key_source = private_key ? 'content' : 'path';
+ const { certificate_chain, private_key, private_key_saved } = data;
+ const certificate_source = certificate_chain
+ ? ENCRYPTION_SOURCE.CONTENT
+ : ENCRYPTION_SOURCE.PATH;
+ const key_source = private_key || private_key_saved
+ ? ENCRYPTION_SOURCE.CONTENT
+ : ENCRYPTION_SOURCE.PATH;
return {
...data,
@@ -41,7 +45,9 @@ class Encryption extends Component {
};
getSubmitValues = (values) => {
- const { certificate_source, key_source, ...config } = values;
+ const {
+ certificate_source, key_source, private_key_saved, ...config
+ } = values;
if (certificate_source === ENCRYPTION_SOURCE.PATH) {
config.certificate_chain = '';
@@ -49,10 +55,15 @@ class Encryption extends Component {
config.certificate_path = '';
}
- if (values.key_source === ENCRYPTION_SOURCE.PATH) {
+ if (key_source === ENCRYPTION_SOURCE.PATH) {
config.private_key = '';
} else {
config.private_key_path = '';
+
+ if (private_key_saved) {
+ config.private_key = '';
+ config.private_key_saved = private_key_saved;
+ }
}
return config;
@@ -71,6 +82,7 @@ class Encryption extends Component {
private_key,
certificate_path,
private_key_path,
+ private_key_saved,
} = encryption;
const initialValues = this.getInitialValues({
@@ -84,6 +96,7 @@ class Encryption extends Component {
private_key,
certificate_path,
private_key_path,
+ private_key_saved,
});
return (
diff --git a/client/src/helpers/twosky.js b/client/src/helpers/twosky.js
index d9534ba6..29583d1f 100644
--- a/client/src/helpers/twosky.js
+++ b/client/src/helpers/twosky.js
@@ -1,3 +1,4 @@
+// eslint-disable-next-line import/no-extraneous-dependencies
import twosky from 'MainRoot/.twosky.json';
export const {
diff --git a/client/src/install/Setup/Submit.js b/client/src/install/Setup/Submit.js
index d3c9f555..a4328d1e 100644
--- a/client/src/install/Setup/Submit.js
+++ b/client/src/install/Setup/Submit.js
@@ -47,7 +47,6 @@ Submit = connect((state) => {
};
})(Submit);
-
export default flow([
withTranslation(),
reduxForm({
diff --git a/internal/home/tls.go b/internal/home/tls.go
index 709ada19..cd13407f 100644
--- a/internal/home/tls.go
+++ b/internal/home/tls.go
@@ -210,15 +210,26 @@ type tlsConfigStatus struct {
// field ordering is important -- yaml fields will mirror ordering from here
type tlsConfig struct {
- tlsConfigStatus `json:",inline"`
+ tlsConfigStatus `json:",inline"`
+ tlsConfigSettingsExt `json:",inline"`
+}
+
+// tlsConfigSettingsExt is used to (un)marshal PrivateKeySaved to ensure that
+// clients don't send and receive previously saved private keys.
+type tlsConfigSettingsExt struct {
tlsConfigSettings `json:",inline"`
+ // If private key saved as a string, we set this flag to true
+ // and omit key from answer.
+ PrivateKeySaved bool `yaml:"-" json:"private_key_saved,inline"`
}
func (t *TLSMod) handleTLSStatus(w http.ResponseWriter, _ *http.Request) {
t.confLock.Lock()
data := tlsConfig{
- tlsConfigSettings: t.conf,
- tlsConfigStatus: t.status,
+ tlsConfigSettingsExt: tlsConfigSettingsExt{
+ tlsConfigSettings: t.conf,
+ },
+ tlsConfigStatus: t.status,
}
t.confLock.Unlock()
marshalTLS(w, data)
@@ -231,19 +242,23 @@ func (t *TLSMod) handleTLSValidate(w http.ResponseWriter, r *http.Request) {
return
}
+ if setts.PrivateKeySaved {
+ setts.PrivateKey = t.conf.PrivateKey
+ }
+
if !WebCheckPortAvailable(setts.PortHTTPS) {
httpError(w, http.StatusBadRequest, "port %d is not available, cannot enable HTTPS on it", setts.PortHTTPS)
return
}
status := tlsConfigStatus{}
- if tlsLoadConfig(&setts, &status) {
+ if tlsLoadConfig(&setts.tlsConfigSettings, &status) {
status = validateCertificates(string(setts.CertificateChainData), string(setts.PrivateKeyData), setts.ServerName)
}
data := tlsConfig{
- tlsConfigSettings: setts,
- tlsConfigStatus: status,
+ tlsConfigSettingsExt: setts,
+ tlsConfigStatus: status,
}
marshalTLS(w, data)
}
@@ -290,16 +305,20 @@ func (t *TLSMod) handleTLSConfigure(w http.ResponseWriter, r *http.Request) {
return
}
+ if data.PrivateKeySaved {
+ data.PrivateKey = t.conf.PrivateKey
+ }
+
if !WebCheckPortAvailable(data.PortHTTPS) {
httpError(w, http.StatusBadRequest, "port %d is not available, cannot enable HTTPS on it", data.PortHTTPS)
return
}
status := tlsConfigStatus{}
- if !tlsLoadConfig(&data, &status) {
+ if !tlsLoadConfig(&data.tlsConfigSettings, &status) {
data2 := tlsConfig{
- tlsConfigSettings: data,
- tlsConfigStatus: t.status,
+ tlsConfigSettingsExt: data,
+ tlsConfigStatus: t.status,
}
marshalTLS(w, data2)
@@ -308,7 +327,7 @@ func (t *TLSMod) handleTLSConfigure(w http.ResponseWriter, r *http.Request) {
status = validateCertificates(string(data.CertificateChainData), string(data.PrivateKeyData), data.ServerName)
- restartHTTPS := t.setConfig(data, status)
+ restartHTTPS := t.setConfig(data.tlsConfigSettings, status)
t.setCertFileTime()
onConfigModified()
@@ -320,8 +339,8 @@ func (t *TLSMod) handleTLSConfigure(w http.ResponseWriter, r *http.Request) {
}
data2 := tlsConfig{
- tlsConfigSettings: data,
- tlsConfigStatus: t.status,
+ tlsConfigSettingsExt: data,
+ tlsConfigStatus: t.status,
}
marshalTLS(w, data2)
@@ -335,7 +354,7 @@ func (t *TLSMod) handleTLSConfigure(w http.ResponseWriter, r *http.Request) {
// goroutine due to the same reason.
if restartHTTPS {
go func() {
- Context.web.TLSConfigChanged(context.Background(), data)
+ Context.web.TLSConfigChanged(context.Background(), data.tlsConfigSettings)
}()
}
}
@@ -514,8 +533,8 @@ func parsePrivateKey(der []byte) (crypto.PrivateKey, string, error) {
}
// unmarshalTLS handles base64-encoded certificates transparently
-func unmarshalTLS(r *http.Request) (tlsConfigSettings, error) {
- data := tlsConfigSettings{}
+func unmarshalTLS(r *http.Request) (tlsConfigSettingsExt, error) {
+ data := tlsConfigSettingsExt{}
err := json.NewDecoder(r.Body).Decode(&data)
if err != nil {
return data, fmt.Errorf("failed to parse new TLS config json: %w", err)
@@ -559,8 +578,8 @@ func marshalTLS(w http.ResponseWriter, data tlsConfig) {
}
if data.PrivateKey != "" {
- encoded := base64.StdEncoding.EncodeToString([]byte(data.PrivateKey))
- data.PrivateKey = encoded
+ data.PrivateKeySaved = true
+ data.PrivateKey = ""
}
err := json.NewEncoder(w).Encode(data)
diff --git a/openapi/CHANGELOG.md b/openapi/CHANGELOG.md
index 9a70bb9a..51592842 100644
--- a/openapi/CHANGELOG.md
+++ b/openapi/CHANGELOG.md
@@ -4,6 +4,13 @@
## v0.107: API changes
+### The new field `"private_key_saved"` in `TlsConfig`
+
+* The new field `"private_key_saved"` in `POST /control/tls/configure`,
+`POST /control/tls/validate` and `GET /control/tls/status` is true if the
+private key was previously saved as a string and now the private key omitted
+from communication between server and client due to security issues.
+
### The new field `"cache_optimistic"` in DNS configuration
* The new optional field `"cache_optimistic"` in `POST /control/dns_config`
diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml
index 3dcbacfa..b60cec6b 100644
--- a/openapi/openapi.yaml
+++ b/openapi/openapi.yaml
@@ -2075,6 +2075,14 @@
'private_key':
'type': 'string'
'description': 'Base64 string with PEM-encoded private key'
+ 'private_key_saved':
+ 'type': 'boolean'
+ 'example': true
+ 'description': >
+ Set to true if the user has previously saved a private key as
+ a string. This is used so that the server and the client don't
+ have to send the private key between each other every time,
+ which might lead to security issues.
'certificate_path':
'type': 'string'
'description': 'Path to certificate file'