From d5c044692a9548a228cc9024b57c4f55f7f53c9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Fri, 21 Aug 2026 14:28:10 +0200 Subject: [PATCH 1/3] Change: Allow to set a tooltip on password input fields Allow to display a tooltip for further information about a password field. --- src/web/components/form/PasswordField.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/web/components/form/PasswordField.tsx b/src/web/components/form/PasswordField.tsx index e3c30ef1b9..171bf433a7 100644 --- a/src/web/components/form/PasswordField.tsx +++ b/src/web/components/form/PasswordField.tsx @@ -18,6 +18,7 @@ interface PasswordFieldProps extends Omit< name?: string; placeholder?: string; title?: string; + toolTipTitle?: string; value?: string; onChange?: (value: string, name?: string) => void; onKeyDown?: (event: React.KeyboardEvent) => void; @@ -31,6 +32,7 @@ const PasswordField = ({ name, placeholder, title, + toolTipTitle, value, onChange, onKeyDown, @@ -48,6 +50,7 @@ const PasswordField = ({ name={name} placeholder={placeholder} styles={{root: {flexGrow: grow}}} + title={toolTipTitle} value={value} onChange={handleChange} onKeyDown={onKeyDown} From e2f6d9198e1e637180c0f81bc4101cc33686c45d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Fri, 21 Aug 2026 14:21:19 +0200 Subject: [PATCH 2/3] Change: Allow to upload encrypted private keys for client cert credentials There are two kinds of private keys for client certificate credentials. Encrypted and unencrypted. Only the encrypted keys need a passphrase to be able to decrypted them. Therefore change the dialog to allow uploading both private key formats and only to enable the passphrase field for encrypted private keys. --- public/locales/gsa-de.json | 6 +- public/locales/gsa-en.json | 2 + public/locales/gsa-it.json | 2 + public/locales/gsa-ja.json | 2 + public/locales/gsa-zh_CN.json | 2 + public/locales/gsa-zh_TW.json | 2 + .../pages/credentials/CredentialDialog.tsx | 125 ++++++++++++------ .../__tests__/CredentialDialog.test.tsx | 84 ++++++++++++ 8 files changed, 186 insertions(+), 39 deletions(-) diff --git a/public/locales/gsa-de.json b/public/locales/gsa-de.json index d753b9850e..1b212aa99d 100644 --- a/public/locales/gsa-de.json +++ b/public/locales/gsa-de.json @@ -1538,6 +1538,7 @@ "Passive": "Passiv", "Passphrase": "Passphrase", "Passphrase for Client Private Key": "Passphrase für privaten Client-Schlüssel", + "Passphrase for encrypted Client Private Key": "Passphrase für verschlüsselten privaten Client-Schlüssel", "Passphrase for private key or PKCS#12 file (if encrypted)": "Passphrase für privaten Schlüssel oder PKCS#12-Datei (falls verschlüsselt)", "Passphrase for Private SSH Key": "Passphrase für privaten SSH-Schlüssel", "Password": "Passwort", @@ -2035,8 +2036,8 @@ "Table: Vulnerabilities by Hosts": "Tabelle: Schwachstellen nach Hosts", "Table: Vulnerabilities by Severity Class": "Tabelle: Schwachstellen nach Schweregradklasse", "Tag": "Tag", - "Tag {{- name}} disabled": "", - "Tag {{- name}} enabled": "", + "Tag {{- name}} disabled": "Tag {{- name}} deaktiviert", + "Tag {{- name}} enabled": "Tag {{- name}} aktiviert", "Tag List": "Tagliste", "Tag: {{name}}": "Tag: {{name}}", "Tags": "Tags", @@ -2093,6 +2094,7 @@ "Th.": "Do.", "The {{entity}} you were looking for could not be found.": "{{entity}} konnte nicht gefunden werden.", "The {{entityType}} cannot be displayed in order to maintain the performance within the browser's capabilities. The report contains too many results. Please decrease the number of results below the threshold of {{threshold}} by applying a more refined filter.": "{{entityType}} können nicht angezeigt werden, um die Leistung innerhalb der Browser-Möglichkeiten zu halten. Der Bericht enthält zu viele Ergebnisse. Bitte reduzieren Sie die Anzahl der Ergebnisse unter die Grenze von {{threshold}}, indem Sie einen präziseren Filter anwenden.", + "The client private key is not encrypted, so no passphrase is required": "Der private Client-Schlüssel ist nicht verschlüsselt, daher ist keine Passphrase erforderlich.", "The connection to this web application is not encrypted, allowing anyone listening to the traffic to steal your credentials.": "Die Verbindung zu dieser Webanwendung ist nicht verschlüsselt, sodass jeder, der den Datenverkehr abhört, Ihre Anmeldeinformationen stehlen kann.", "The current number of {{hostsCount}} hosts exceeds the maximum of {{maxHosts}}. Please apply a more specific filter.": "Die aktulle Anzahl von {{hostsCount}} Hosts überschreitet das Maximum von {{maxHosts}}. Bitte wenden sie einen gezielteren Filter an.", "The default address is either your computer or your network gateway.": "Die Standard-Adresse ist entweder die Ihres Computers oder die Ihres Netzwerk-Gateways.", diff --git a/public/locales/gsa-en.json b/public/locales/gsa-en.json index ee8976f950..28caf8dc17 100644 --- a/public/locales/gsa-en.json +++ b/public/locales/gsa-en.json @@ -1538,6 +1538,7 @@ "Passive": "Passive", "Passphrase": "Passphrase", "Passphrase for Client Private Key": "Passphrase for Client Private Key", + "Passphrase for encrypted Client Private Key": "Passphrase for encrypted Client Private Key", "Passphrase for private key or PKCS#12 file (if encrypted)": "Passphrase for private key or PKCS#12 file (if encrypted)", "Passphrase for Private SSH Key": "Passphrase for Private SSH Key", "Password": "Password", @@ -2093,6 +2094,7 @@ "Th.": "Th.", "The {{entity}} you were looking for could not be found.": "The {{entity}} you were looking for could not be found.", "The {{entityType}} cannot be displayed in order to maintain the performance within the browser's capabilities. The report contains too many results. Please decrease the number of results below the threshold of {{threshold}} by applying a more refined filter.": "The {{entityType}} cannot be displayed in order to maintain the performance within the browser's capabilities. The report contains too many results. Please decrease the number of results below the threshold of {{threshold}} by applying a more refined filter.", + "The client private key is not encrypted, so no passphrase is required": "The client private key is not encrypted, so no passphrase is required", "The connection to this web application is not encrypted, allowing anyone listening to the traffic to steal your credentials.": "The connection to this web application is not encrypted, allowing anyone listening to the traffic to steal your credentials.", "The current number of {{hostsCount}} hosts exceeds the maximum of {{maxHosts}}. Please apply a more specific filter.": "The current number of {{hostsCount}} hosts exceeds the maximum of {{maxHosts}}. Please apply a more specific filter.", "The default address is either your computer or your network gateway.": "The default address is either your computer or your network gateway.", diff --git a/public/locales/gsa-it.json b/public/locales/gsa-it.json index 92ccf0c825..c853bf3b59 100644 --- a/public/locales/gsa-it.json +++ b/public/locales/gsa-it.json @@ -1538,6 +1538,7 @@ "Passive": "Passivo", "Passphrase": "Passphrase", "Passphrase for Client Private Key": "Passphrase per la chiave privata client", + "Passphrase for encrypted Client Private Key": "", "Passphrase for private key or PKCS#12 file (if encrypted)": "Passphrase per la chiave privata o il file PKCS#12 (se cifrato)", "Passphrase for Private SSH Key": "Passphrase per la chiave privata SSH", "Password": "Password", @@ -2093,6 +2094,7 @@ "Th.": "Gio.", "The {{entity}} you were looking for could not be found.": "La {{entity}} cercata non è stata trovata.", "The {{entityType}} cannot be displayed in order to maintain the performance within the browser's capabilities. The report contains too many results. Please decrease the number of results below the threshold of {{threshold}} by applying a more refined filter.": "Il {{entityType}} non può essere visualizzato per mantenere le prestazioni entro le capacità del browser. Il rapporto contiene troppi risultati. Riduci il numero di risultati al di sotto della soglia di {{threshold}} applicando un filtro più preciso.", + "The client private key is not encrypted, so no passphrase is required": "", "The connection to this web application is not encrypted, allowing anyone listening to the traffic to steal your credentials.": "La connessione a questa applicazione web non è crittografata, consentendo a chiunque ascolti il traffico di rubare le tue credenziali.", "The current number of {{hostsCount}} hosts exceeds the maximum of {{maxHosts}}. Please apply a more specific filter.": "Il numero attuale di {{hostsCount}} host supera il massimo di {{maxHosts}}. Applica un filtro più specifico.", "The default address is either your computer or your network gateway.": "L'indirizzo predefinito è il tuo computer o il gateway di rete.", diff --git a/public/locales/gsa-ja.json b/public/locales/gsa-ja.json index 3ec6d87267..bb0ef31f49 100644 --- a/public/locales/gsa-ja.json +++ b/public/locales/gsa-ja.json @@ -1538,6 +1538,7 @@ "Passive": "パッシブ", "Passphrase": "パスフレーズ", "Passphrase for Client Private Key": "クライアント秘密鍵のパスフレーズ", + "Passphrase for encrypted Client Private Key": "", "Passphrase for private key or PKCS#12 file (if encrypted)": "秘密鍵またはPKCS#12ファイルのパスフレーズ (暗号化されている場合)", "Passphrase for Private SSH Key": "SSH秘密鍵のパスフレーズ", "Password": "パスワード", @@ -2093,6 +2094,7 @@ "Th.": "木", "The {{entity}} you were looking for could not be found.": "お探しの{{entity}}が見つかりませんでした。", "The {{entityType}} cannot be displayed in order to maintain the performance within the browser's capabilities. The report contains too many results. Please decrease the number of results below the threshold of {{threshold}} by applying a more refined filter.": "ブラウザーの性能の範囲内でパフォーマンスを維持するため、{{entityType}}を表示できません。レポートに含まれる結果が多すぎます。より絞り込んだフィルターを適用して、結果の数をしきい値である{{threshold}}未満に減らしてください。", + "The client private key is not encrypted, so no passphrase is required": "", "The connection to this web application is not encrypted, allowing anyone listening to the traffic to steal your credentials.": "このWebアプリケーションへの接続は暗号化されていないため、通信を傍受した第三者にクレデンシャルを盗まれるおそれがあります。", "The current number of {{hostsCount}} hosts exceeds the maximum of {{maxHosts}}. Please apply a more specific filter.": "現在のホスト数{{hostsCount}}は、最大値{{maxHosts}}を超えています。より具体的なフィルターを適用してください。", "The default address is either your computer or your network gateway.": "デフォルトのアドレスは、お使いのコンピューターまたはネットワークゲートウェイのいずれかです。", diff --git a/public/locales/gsa-zh_CN.json b/public/locales/gsa-zh_CN.json index e0c5ef6d79..de5e9443bf 100644 --- a/public/locales/gsa-zh_CN.json +++ b/public/locales/gsa-zh_CN.json @@ -1538,6 +1538,7 @@ "Passive": "被动", "Passphrase": "密码短语", "Passphrase for Client Private Key": "客户端私钥密码短语", + "Passphrase for encrypted Client Private Key": "", "Passphrase for private key or PKCS#12 file (if encrypted)": "私钥或PKCS#12文件的密码短语(如果加密)", "Passphrase for Private SSH Key": "SSH私钥密码短语", "Password": "密码", @@ -2093,6 +2094,7 @@ "Th.": "周四", "The {{entity}} you were looking for could not be found.": "找不到您要查找的{{entity}}.", "The {{entityType}} cannot be displayed in order to maintain the performance within the browser's capabilities. The report contains too many results. Please decrease the number of results below the threshold of {{threshold}} by applying a more refined filter.": "无法显示{{entityType}}以保持浏览器功能范围内的性能.报告包含太多结果.请通过应用更精细的筛选器将结果数减少到{{threshold}}以下", + "The client private key is not encrypted, so no passphrase is required": "", "The connection to this web application is not encrypted, allowing anyone listening to the traffic to steal your credentials.": "此Web应用程序的连接未加密,允许任何监听流量的人窃取您的凭据。", "The current number of {{hostsCount}} hosts exceeds the maximum of {{maxHosts}}. Please apply a more specific filter.": "当前主机的数量{{hostsCount}}超过了最大值{{maxHosts}}.请应用更具体的筛选器.", "The default address is either your computer or your network gateway.": "默认地址是您的计算机地址或网关.", diff --git a/public/locales/gsa-zh_TW.json b/public/locales/gsa-zh_TW.json index 781a4c0142..eef993b54b 100644 --- a/public/locales/gsa-zh_TW.json +++ b/public/locales/gsa-zh_TW.json @@ -1538,6 +1538,7 @@ "Passive": "", "Passphrase": "", "Passphrase for Client Private Key": "", + "Passphrase for encrypted Client Private Key": "", "Passphrase for private key or PKCS#12 file (if encrypted)": "", "Passphrase for Private SSH Key": "", "Password": "密碼", @@ -2093,6 +2094,7 @@ "Th.": "四", "The {{entity}} you were looking for could not be found.": "", "The {{entityType}} cannot be displayed in order to maintain the performance within the browser's capabilities. The report contains too many results. Please decrease the number of results below the threshold of {{threshold}} by applying a more refined filter.": "", + "The client private key is not encrypted, so no passphrase is required": "", "The connection to this web application is not encrypted, allowing anyone listening to the traffic to steal your credentials.": "", "The current number of {{hostsCount}} hosts exceeds the maximum of {{maxHosts}}. Please apply a more specific filter.": "", "The default address is either your computer or your network gateway.": "", diff --git a/src/web/pages/credentials/CredentialDialog.tsx b/src/web/pages/credentials/CredentialDialog.tsx index e38dd25b6b..48fe0e8ebb 100644 --- a/src/web/pages/credentials/CredentialDialog.tsx +++ b/src/web/pages/credentials/CredentialDialog.tsx @@ -50,6 +50,7 @@ interface CredentialDialogValues { certificate?: File | null; credentialType: CredentialType; hostIdentifier?: string; + passphrase?: string; privateKey?: File | null; privacyHostIdentifier?: string; publicKey?: File | null; @@ -65,7 +66,6 @@ interface CredentialDialogDefaultValues { id?: string; kdcs?: string[]; name: string; - passphrase?: string; password?: string; privacyAlgorithm?: SNMPPrivacyAlgorithmType; privacyHostIdentifier?: string; @@ -109,6 +109,14 @@ const validateFile = async ( errorMessage: string, ) => { const content = await file.text(); + return validateContent(content, line, errorMessage); +}; + +const validateContent = ( + content: string, + line: string | RegExp, + errorMessage: string, +) => { if (!isString(content)) { throw new Error(errorMessage); } @@ -122,7 +130,9 @@ const validateFile = async ( const PGP_PUBLIC_KEY_LINE = '-----BEGIN PGP PUBLIC KEY BLOCK-----'; const CLIENT_CERTIFICATE_LINE = '-----BEGIN CERTIFICATE-----'; -const CLIENT_PRIVATE_KEY_LINE = '-----BEGIN PRIVATE KEY-----'; +const CLIENT_ENCRYPTED_PRIVATE_KEY_LINE = + '-----BEGIN ENCRYPTED PRIVATE KEY-----'; +const CLIENT_PRIVATE_KEY_REGEX = /-----BEGIN (?:ENCRYPTED )?PRIVATE KEY-----/; const SSH_KEY_REGEX = /-----BEGIN \w+ PRIVATE KEY-----/; const CredentialDialog = ({ @@ -136,7 +146,7 @@ const CredentialDialog = ({ credentialType: initialCredentialType, hostIdentifier, name, - passphrase, + passphrase: initialPassphrase, password, publicKey: initialPublicKey, privacyAlgorithm = SNMP_PRIVACY_ALGORITHM_AES, @@ -151,7 +161,6 @@ const CredentialDialog = ({ onSave, }: CredentialDialogProps) => { const [_] = useTranslation(); - title = title ?? _('New Credential'); name = name ?? _('Unnamed'); @@ -173,11 +182,17 @@ const CredentialDialog = ({ const [publicKey, setPublicKey] = useState( initialPublicKey, ); + const [passphrase, setPassphrase] = useState( + initialPassphrase, + ); const [changeCommunity, setChangeCommunity] = useState(false); const [changePassphrase, setChangePassphrase] = useState(false); const [changePassword, setChangePassword] = useState(false); const [changePrivacyPassword, setChangePrivacyPassword] = useState(false); + // if we don't know if the private key is encrypted, we assume it is, so that the user can change the passphrase if they want to + const [isEncryptedPrivateKey, setIsEncryptedPrivateKey] = + useState(true); const isEdit = isDefined(credential); @@ -233,11 +248,20 @@ const CredentialDialog = ({ const handleClientKeyChange = async (file: File | undefined) => { try { if (isDefined(file)) { - await validateFile( - file, - CLIENT_PRIVATE_KEY_LINE, + const content = await file.text(); + validateContent( + content, + CLIENT_PRIVATE_KEY_REGEX, _('Not a valid Client Private Key file'), ); + if (content.startsWith(CLIENT_ENCRYPTED_PRIVATE_KEY_LINE)) { + setIsEncryptedPrivateKey(true); + } else { + setIsEncryptedPrivateKey(false); + setPassphrase(undefined); + } + } else { + setIsEncryptedPrivateKey(true); } setPrivateKey(file ?? null); } catch (error) { @@ -315,7 +339,6 @@ const CredentialDialog = ({ community, credentialLogin, name, - passphrase, password, privacyAlgorithm, privacyHostIdentifier, @@ -331,6 +354,7 @@ const CredentialDialog = ({ values={{ autogenerate, credentialType: cType as CredentialType, + passphrase, publicKey, privateKey, certificate, @@ -465,41 +489,19 @@ const CredentialDialog = ({ )} - {state.credentialType === CERTIFICATE_CREDENTIAL_TYPE && ( - - )} - - {(state.credentialType === USERNAME_SSH_KEY_CREDENTIAL_TYPE || - state.credentialType === CERTIFICATE_CREDENTIAL_TYPE) && ( + {state.credentialType === USERNAME_SSH_KEY_CREDENTIAL_TYPE && ( <> {isEdit && ( @@ -514,9 +516,7 @@ const CredentialDialog = ({ )} + {state.credentialType === CERTIFICATE_CREDENTIAL_TYPE && ( + <> + + + + + {isEdit && isEncryptedPrivateKey && ( + + checked={changePassphrase} + checkedValue={true} + name="changePassphrase" + title={_('Replace existing passphrase with')} + unCheckedValue={false} + onChange={setChangePassphrase} + /> + )} + + + + )} + {state.credentialType === SNMP_CREDENTIAL_TYPE && ( <> diff --git a/src/web/pages/credentials/__tests__/CredentialDialog.test.tsx b/src/web/pages/credentials/__tests__/CredentialDialog.test.tsx index 44b5039b15..98b07ddf04 100644 --- a/src/web/pages/credentials/__tests__/CredentialDialog.test.tsx +++ b/src/web/pages/credentials/__tests__/CredentialDialog.test.tsx @@ -12,6 +12,7 @@ import { within, rendererWith, fireEvent, + waitFor, } from 'web/testing'; import Features from 'gmp/capabilities/features'; import Credential, { @@ -220,11 +221,16 @@ describe('CredentialDialog tests', () => { expect(select).toHaveValue('Username + SSH Key'); + const username = screen.getByName('credentialLogin'); + expect(username).toHaveValue(''); + const password = screen.getByName('passphrase'); expect(password).toHaveValue(''); + expect(screen.getByText('Passphrase for Private SSH Key')).toBeVisible(); const privateKey = screen.getByName('privateKey'); expect(privateKey).toHaveAttribute('type', 'file'); + expect(screen.getByRole('button', {name: 'Private SSH Key'})).toBeVisible(); }); test('should disable private key field when autogenerate is enabled for Username + SSH Key', () => { @@ -393,6 +399,84 @@ describe('CredentialDialog tests', () => { const passphrase = screen.getByName('passphrase'); expect(passphrase).toHaveValue(''); expect(passphrase).toHaveAttribute('type', 'password'); + expect(screen.getByText('Passphrase for Client Private Key')).toBeVisible(); + }); + + test('should save client certificate and unencrypted private key', async () => { + const {render} = rendererWith({ + gmp: createGmp(), + }); + const handleSave = testing.fn(); + + render( + , + ); + + const certificate = new File( + ['-----BEGIN CERTIFICATE-----\ncertificate'], + 'certificate.pem', + {type: 'application/x-pem-file'}, + ); + const privateKey = new File( + ['-----BEGIN PRIVATE KEY-----\nprivate key'], + 'private-key.pem', + {type: 'application/x-pem-file'}, + ); + + fireEvent.change(screen.getByName('certificate'), { + target: {files: [certificate]}, + }); + fireEvent.change(screen.getByName('privateKey'), { + target: {files: [privateKey]}, + }); + + await waitFor(() => { + expect(screen.getByName('passphrase')).toBeDisabled(); + }); + + fireEvent.click(screen.getDialogSaveButton()); + + expect(handleSave).toHaveBeenCalledWith( + expect.objectContaining({ + certificate, + credentialType: CERTIFICATE_CREDENTIAL_TYPE, + passphrase: undefined, + privateKey, + }), + ); + }); + + test('should enable passphrase for encrypted client private key', async () => { + const {render} = rendererWith({ + gmp: createGmp(), + }); + + render( + , + ); + + const privateKey = new File( + ['-----BEGIN ENCRYPTED PRIVATE KEY-----\nencrypted key'], + 'encrypted-private-key.pem', + {type: 'application/x-pem-file'}, + ); + fireEvent.change(screen.getByName('privateKey'), { + target: {files: [privateKey]}, + }); + + await waitFor(() => { + expect(screen.getByName('passphrase')).not.toBeDisabled(); + }); + expect( + screen.getByTitle('Passphrase for encrypted Client Private Key'), + ).toBeInTheDocument(); }); test('should handle replace password interactions correctly', () => { From 13b851eb3db95dd16e3e408e271626078c7ed5fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Mon, 24 Aug 2026 08:47:23 +0200 Subject: [PATCH 3/3] Add italian translations for credential dialog changes Co-authored-by: Daniele Mangiagli --- public/locales/gsa-it.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/locales/gsa-it.json b/public/locales/gsa-it.json index c853bf3b59..3aeccac232 100644 --- a/public/locales/gsa-it.json +++ b/public/locales/gsa-it.json @@ -1538,7 +1538,7 @@ "Passive": "Passivo", "Passphrase": "Passphrase", "Passphrase for Client Private Key": "Passphrase per la chiave privata client", - "Passphrase for encrypted Client Private Key": "", + "Passphrase for encrypted Client Private Key": "Passphrase per la chiave privata client cifrata", "Passphrase for private key or PKCS#12 file (if encrypted)": "Passphrase per la chiave privata o il file PKCS#12 (se cifrato)", "Passphrase for Private SSH Key": "Passphrase per la chiave privata SSH", "Password": "Password", @@ -2094,7 +2094,7 @@ "Th.": "Gio.", "The {{entity}} you were looking for could not be found.": "La {{entity}} cercata non è stata trovata.", "The {{entityType}} cannot be displayed in order to maintain the performance within the browser's capabilities. The report contains too many results. Please decrease the number of results below the threshold of {{threshold}} by applying a more refined filter.": "Il {{entityType}} non può essere visualizzato per mantenere le prestazioni entro le capacità del browser. Il rapporto contiene troppi risultati. Riduci il numero di risultati al di sotto della soglia di {{threshold}} applicando un filtro più preciso.", - "The client private key is not encrypted, so no passphrase is required": "", + "The client private key is not encrypted, so no passphrase is required": "La chiave privata del client non è crittografata, quindi non è richiesta alcuna passphrase", "The connection to this web application is not encrypted, allowing anyone listening to the traffic to steal your credentials.": "La connessione a questa applicazione web non è crittografata, consentendo a chiunque ascolti il traffico di rubare le tue credenziali.", "The current number of {{hostsCount}} hosts exceeds the maximum of {{maxHosts}}. Please apply a more specific filter.": "Il numero attuale di {{hostsCount}} host supera il massimo di {{maxHosts}}. Applica un filtro più specifico.", "The default address is either your computer or your network gateway.": "L'indirizzo predefinito è il tuo computer o il gateway di rete.",