From 039dddd1075dd29a33604cd363e2c0bf1bc58881 Mon Sep 17 00:00:00 2001 From: Bruno Date: Sun, 6 Sep 2026 22:17:59 -0300 Subject: [PATCH 1/5] improved localization for pt-BR (#804) Co-authored-by: Max Goedjen --- .../Packages/Resources/Localizable.xcstrings | 268 ++++++++++++------ 1 file changed, 185 insertions(+), 83 deletions(-) diff --git a/Sources/Packages/Resources/Localizable.xcstrings b/Sources/Packages/Resources/Localizable.xcstrings index d66c69a..0ec7f75 100644 --- a/Sources/Packages/Resources/Localizable.xcstrings +++ b/Sources/Packages/Resources/Localizable.xcstrings @@ -490,8 +490,8 @@ }, "pt-BR" : { "stringUnit" : { - "state" : "new", - "value" : "Build Log" + "state" : "translated", + "value" : "Log de Build" } }, "ro" : { @@ -675,8 +675,8 @@ }, "pt-BR" : { "stringUnit" : { - "state" : "new", - "value" : "About Secretive" + "state" : "translated", + "value" : "Sobre o Secretive" } }, "ro" : { @@ -860,8 +860,8 @@ }, "pt-BR" : { "stringUnit" : { - "state" : "new", - "value" : "Secretive is Open Source and MIT Licensed" + "state" : "translated", + "value" : "O Secretive é Open Source e distribuído sob a licença MIT" } }, "ro" : { @@ -1045,8 +1045,8 @@ }, "pt-BR" : { "stringUnit" : { - "state" : "new", - "value" : "Special thanks our [Contributors](%1$(contributorsLink)@) and [Sponsors](%2$(sponsorsLink)@)" + "state" : "translated", + "value" : "Agradecimentos especiais aos nossos [Contribuidores](%1$(contributorsLink)@) e [Patrocinadores](%2$(sponsorsLink)@)" } }, "ro" : { @@ -1230,8 +1230,8 @@ }, "pt-BR" : { "stringUnit" : { - "state" : "new", - "value" : "View on GitHub" + "state" : "translated", + "value" : "Ver no GitHub" } }, "ro" : { @@ -1416,7 +1416,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Secretive não foi capaz de obter o SecretAgent para iniciar. Por favor tente reiniciar seu Mac e se isso não resolver, registre um problema em nosso GitHub." + "value" : "O Secretive não foi capaz de iniciar o SecretAgent. Por favor, tente reiniciar seu Mac e, se isso não resolver, registre uma issue no GitHub." } }, "ro" : { @@ -1601,7 +1601,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Desabilitar o Agente" + "value" : "Desativar o Agente" } }, "ro" : { @@ -2156,7 +2156,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Caminho do Socket" + "value" : "Caminho do socket" } }, "ro" : { @@ -2526,7 +2526,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Agente iniciando" + "value" : "Iniciando Agente" } }, "ro" : { @@ -2896,7 +2896,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "SecretAgent é um processo que é executado em segundo plano para assinar pedidos, então não há necessidade de manter Secretive aberto o tempo todo.\n\n**Secretive não funcionará corretamente a menos que o agente esteja instalado e executando.**" + "value" : "O SecretAgent é um processo que roda em segundo plano para assinar pedidos, sem precisar manter o Secretive aberto o tempo todo.\n\n**O Secretive não funcionará corretamente a menos que o agente esteja instalado e rodando.**" } }, "ro" : { @@ -3081,7 +3081,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Agent não está rodando" + "value" : "O Agente não está rodando" } }, "ro" : { @@ -3266,7 +3266,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "SecretAgent é um processo que roda em background para assinar requisições para que você não precise manter o Secretive aberto a todo momento.\n\n**Você pode fechar o Secretive e tudo continuará funcionando.**" + "value" : "O SecretAgent é um processo que roda em segundo plano para assinar pedidos, sem precisar manter o Secretive aberto o tempo todo.\n\n**Você pode fechar o Secretive e tudo continuará funcionando.**" } }, "ro" : { @@ -3451,7 +3451,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Secret Agent está rodando" + "value" : "O SecretAgent está rodando" } }, "ro" : { @@ -3636,7 +3636,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Agent está rodando" + "value" : "O Agente está rodando" } }, "ro" : { @@ -4375,8 +4375,8 @@ }, "pt-BR" : { "stringUnit" : { - "state" : "new", - "value" : "Later" + "state" : "translated", + "value" : "Depois" } }, "ro" : { @@ -4561,7 +4561,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Secretive necessita estar no seu diretório de Aplicações para funcionar corretamente. Por favor mova-o e abra novamente." + "value" : "O Secretive precisa estar no seu diretório de Aplicativos para funcionar corretamente. Por favor, mova-o e abra novamente." } }, "ro" : { @@ -4745,8 +4745,8 @@ }, "pt-BR" : { "stringUnit" : { - "state" : "new", - "value" : "Quit" + "state" : "translated", + "value" : "Encerrar" } }, "ro" : { @@ -4931,7 +4931,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Secretive não está no diretório de Aplicações" + "value" : "O Secretive não está no diretório de Aplicativos" } }, "ro" : { @@ -5117,7 +5117,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "destravar segredo “%1$(secretName)@” for %2$(duration)@" + "value" : "destrancar o segredo “%1$(secretName)@” por %2$(duration)@" } }, "ro" : { @@ -5489,7 +5489,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "assinar requisição a partir do “%1$(appName)@” utilizando o segredo “%2$(secretName)@”" + "value" : "assinar um pedido de “%1$(appName)@” usando o segredo “%2$(secretName)@”" } }, "ro" : { @@ -5556,6 +5556,12 @@ "state" : "translated", "value" : "Critical Options" } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Opções críticas" + } } } }, @@ -5567,6 +5573,12 @@ "state" : "translated", "value" : "Extensions" } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Extensões" + } } } }, @@ -5578,6 +5590,12 @@ "state" : "translated", "value" : "Key ID" } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "ID da Chave" + } } } }, @@ -5589,6 +5607,12 @@ "state" : "translated", "value" : "Certificate Path" } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Caminho do Certificado" + } } } }, @@ -5600,6 +5624,12 @@ "state" : "translated", "value" : "Principals" } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Entidades Principais" + } } } }, @@ -5611,6 +5641,12 @@ "state" : "translated", "value" : "Serial Number" } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Número de Série" + } } } }, @@ -5622,6 +5658,12 @@ "state" : "translated", "value" : "Public Key Fingerprint" } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Impressão Digital da Chave Pública" + } } } }, @@ -5633,6 +5675,12 @@ "state" : "translated", "value" : "Signing CA Fingerprint" } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Impressão Digital da AC de Assinatura" + } } } }, @@ -5644,6 +5692,12 @@ "state" : "translated", "value" : "Valid After" } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Válido após" + } } } }, @@ -5655,6 +5709,12 @@ "state" : "translated", "value" : "Valid Until" } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Válido até" + } } } }, @@ -5666,6 +5726,12 @@ "state" : "translated", "value" : "Validity Range" } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Intervalo de validade" + } } } }, @@ -5798,7 +5864,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Clique para Copiar" + "value" : "Clique para copiar" } }, "ro" : { @@ -6353,7 +6419,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Se você mudar suas configurações de biometria de _qualquer forma_, incluindo adicionar uma nova impressão digital, esta chave não estará mais acessível." + "value" : "Se você mudar suas configurações de biometria de _qualquer forma_, incluindo adicionar uma nova impressão digital, esta chave não será mais acessível." } }, "ro" : { @@ -6908,7 +6974,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Isto é exibido ao final da sua chave pública. É geralmente um endereço de email." + "value" : "Isso é exibido ao final da sua chave pública. É geralmente um endereço de email." } }, "ro" : { @@ -7462,8 +7528,8 @@ }, "pt-BR" : { "stringUnit" : { - "state" : "new", - "value" : "Unavailable on this version of macOS" + "state" : "translated", + "value" : "Indisponível nessa versão do macOS" } }, "ro" : { @@ -7648,7 +7714,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Aviso: Chaves ML-DSA são muito novas e não são suportadas por muitos servidores ainda. Por favor, verifique se o servidor que você estará utilizando esta chave aceita chaves ML-DSA." + "value" : "Aviso: Chaves ML-DSA são muito novas e ainda não suportadas por vários servidores. Por favor, verifique se o servidor em que você usará essa chave aceita chaves ML-DSA." } }, "ro" : { @@ -8203,7 +8269,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Autenticação não é requerida enquanto seu Mac estiver destravado, mas você será notificado quando um segredo for utilizado." + "value" : "Nenhuma autenticação é solicitada enquanto seu Mac estiver desbloqueado, mas você será notificado quando um segredo for usado." } }, "ro" : { @@ -9128,7 +9194,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Você será requerido a autenticar utilizando Touch ID, Apple Watch ou senha antes de cada uso." + "value" : "Você será solicitado a autenticar usando Touch ID, Apple Watch ou senha a cada uso." } }, "ro" : { @@ -9313,7 +9379,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Requer Autenticação" + "value" : "Requer autenticação" } }, "ro" : { @@ -9498,7 +9564,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Criar um Novo Segredo" + "value" : "Criar um novo Segredo" } }, "ro" : { @@ -9683,7 +9749,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Não Apagar" + "value" : "Não apagar" } }, "ro" : { @@ -10053,7 +10119,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Se você deletar %1$(secretName)@, você não será permitido recuperá-lo. Digite “%2$(confirmSecretName)@” para confirmar." + "value" : "Se você apagar %1$(secretName)@, não poderá recuperá-lo. Digite “%2$(confirmSecretName)@” para confirmar." } }, "ro" : { @@ -10238,7 +10304,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Deletar %1$(name)@?" + "value" : "Apagar %1$(name)@?" } }, "ro" : { @@ -10608,7 +10674,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Renomear" + "value" : "Salvar" } }, "ro" : { @@ -10793,7 +10859,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Criar um novo clicando aqui." + "value" : "Crie um novo clicando aqui." } }, "ro" : { @@ -11163,7 +11229,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Parece que você pode ter atualizado recentemente o macOS. Às vezes, isto coloca o Secure Enclave em um estado estranho, e talvez você precise reiniciar seu Mac antes que as coisas comecem a funcionar novamente." + "value" : "Parece que você pode ter atualizado o macOS recentemente. Às vezes, isso coloca o Secure Enclave em um estado estranho, e talvez você precise reiniciar seu Mac antes que as coisas comecem a funcionar novamente." } }, "ro" : { @@ -11348,7 +11414,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Credenciais faltando?" + "value" : "Segredos faltando?" } }, "ro" : { @@ -11533,7 +11599,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Utilize sua ferramenta de gestão de Smart Card para criar um segredo." + "value" : "Use a ferramenta de gestão do seu Smart Card para criar um segredo." } }, "ro" : { @@ -11718,7 +11784,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Secretive suporta chaves EC256, EC384, RSA1024 e RSA2048." + "value" : "O Secretive suporta chaves EC256, EC384 e RSA2048." } }, "ro" : { @@ -12816,7 +12882,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Há uma lista de instruções para aplicativos no GitHub, mantida pela comunidade. Se o aplicativo que você está procurando não é suportado, registre um ticket para que a comunidade possa ajudar." + "value" : "Há uma lista de instruções para aplicativos no GitHub, mantida pela comunidade. Se o aplicativo que você está procurando não é suportado, registre uma issue e a comunidade deve conseguir ajudar." } }, "ro" : { @@ -13001,7 +13067,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Há uma lista de instruções para aplicativos no GitHub, mantida pela comunidade. Se o aplicativo que você está procurando não é suportado, registre um ticket para que a comunidade possa ajudar." + "value" : "Há uma lista de instruções para shells no GitHub, mantida pela comunidade. Se o shell que você está procurando não é suportado, registre uma issue e a comunidade deve conseguir ajudar." } }, "ro" : { @@ -13069,6 +13135,12 @@ "state" : "translated", "value" : "your_email@example.com" } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "seu_email@exemplo.com.br" + } } } }, @@ -13080,6 +13152,12 @@ "state" : "translated", "value" : "The email address you set when you configured git (visible in gitconfig)." } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "O endereço de email que você definiu quando configurou o git (visível no gitconfig)." + } } } }, @@ -13091,6 +13169,12 @@ "state" : "translated", "value" : "Email Address" } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Endereço de email" + } } } }, @@ -13220,7 +13304,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Você precisará criar uma Credencial antes de configurar esta ação." + "value" : "Você precisará criar um Segredo antes de configurar essa ação." } }, "ro" : { @@ -13405,7 +13489,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Configurar Usando Credencial" + "value" : "Configurar usando Segredo" } }, "ro" : { @@ -13590,7 +13674,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Sem Credenciais" + "value" : "Sem Segredo" } }, "ro" : { @@ -13775,7 +13859,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Credencial" + "value" : "Segredo" } }, "ro" : { @@ -13960,7 +14044,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Você pode configurar mais de uma ferramenta, eles geralmente não interferem uns com os outros." + "value" : "Você pode configurar mais de uma ferramenta, elas geralmente não interferem umas com as outras." } }, "ro" : { @@ -14515,7 +14599,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Se você estiver tentando registrar seus commits no git, configure o Git Signing." + "value" : "Se você estiver tentando assinar seus commits no git, configure Assinatura no Git." } }, "ro" : { @@ -14700,7 +14784,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Se você está tentando configurar qualquer coisa em sua linha de comando para usar com Secretive, configure seu shell." + "value" : "Se você está tentando configurar qualquer coisa em sua linha de comando para usar o Secretive, configure seu shell." } }, "ro" : { @@ -14885,7 +14969,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Se você não sabia qual shell você usa e não alterou, você provavelmente está usando `%(shellName)@`." + "value" : "Se você não sabe qual shell usa e não o alterou, você provavelmente está usando `%(shellName)@`." } }, "ro" : { @@ -15255,7 +15339,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Configurando Ferramentas para Secretive" + "value" : "Configurando ferramentas para o Secretive" } }, "ro" : { @@ -15440,7 +15524,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "A maioria das ferramentas tentará procurar chaves SSH no disco em `~/.ssh`. Para usar o Secretive, precisamos configurar essas ferramentas para comunicar com o Secretive." + "value" : "A maioria das ferramentas tentará procurar chaves SSH no disco em `~/.ssh`. Para usar o Secretive, precisamos configurar essas ferramentas para falarem com o Secretive ao invés disso." } }, "ro" : { @@ -15625,7 +15709,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "O que devo Configurar?" + "value" : "O que devo configurar?" } }, "ro" : { @@ -16736,7 +16820,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Arquivo de Configuração" + "value" : "Arquivo de configuração" } }, "ro" : { @@ -17106,7 +17190,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Você pode pedir ao SSH para usar uma chave específica para um determinado host. Consulte a documentação na web para mais detalhes." + "value" : "Você pode dizer ao SSH para usar uma chave específica com determinado host. Consulte a documentação na web para mais detalhes." } }, "ro" : { @@ -17848,7 +17932,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Assinatura Git" + "value" : "Assinatura no Git" } }, "ro" : { @@ -18405,7 +18489,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Visualizar no GitHub" + "value" : "Ver no GitHub" } }, "ro" : { @@ -18590,7 +18674,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Ver Documentação na Web" + "value" : "Ver documentação na web" } }, "ro" : { @@ -19145,7 +19229,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Sem Armazenamento Seguro Disponível" + "value" : "Sem Armazenamento Seguro disponível" } }, "ro" : { @@ -19330,7 +19414,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Se você está buscando adicionar um para seu Mac, o YubiKey 5 Series é muito bom." + "value" : "Se você procura adicionar um para seu Mac, o YubiKey 5 Series é muito bom." } }, "ro" : { @@ -19516,7 +19600,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Deixar Destrancado" + "value" : "Manter destrancado" } }, "ro" : { @@ -19702,7 +19786,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Não Destravar" + "value" : "Não destrancar" } }, "ro" : { @@ -19769,6 +19853,12 @@ "state" : "translated", "value" : "Name" } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nome" + } } } }, @@ -19780,6 +19870,12 @@ "state" : "translated", "value" : "Certificate Name" } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nome do Certificado" + } } } }, @@ -19976,6 +20072,12 @@ "state" : "translated", "value" : "Matching Certificates" } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Certificados correspondentes" + } } } }, @@ -21030,7 +21132,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Renomear" + "value" : "Editar" } }, "ro" : { @@ -21579,7 +21681,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Este aplicativo de ajuda é chamado **Secret Agent** e você pode vê-lo no Monitor de Atividades de tempo em tempo." + "value" : "Esse aplicativo auxiliar se chama **Secret Agent** e você pode vê-lo no Monitor de Atividades de tempos em tempos." } }, "ro" : { @@ -21764,7 +21866,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Secretive precisa configurar um aplicativo de ajuda para funcionar corretamente. Isso irá assinar requisições de clientes SSH no plano de fundo para que você não precise manter o aplicativo Secretive aberto." + "value" : "O Secretive precisa configurar um aplicativo auxiliar para funcionar corretamente. Ele irá assinar pedidos de clientes SSH em segundo plano, para que você não precise manter o aplicativo principal do Secretive aberto." } }, "ro" : { @@ -22134,7 +22236,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Configurar Agent" + "value" : "Configurar Agente" } }, "ro" : { @@ -23059,7 +23161,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Secretive irá periodicamente verificar com o GitHub para verificar se existe uma nova versão. Se você ver alguma requisição de rede para o GitHub, este é o porque." + "value" : "O Secretive vai verificar o GitHub periodicamente para saber se há uma nova versão. Se notar requisições de rede para o GitHub, esse é o motivo." } }, "ro" : { @@ -23800,7 +23902,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Utilizando o segredo %1$(secretName)@" + "value" : "Usando o segredo %1$(secretName)@" } }, "ro" : { @@ -23986,7 +24088,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Requisição Assinada fr %1$(appName)@" + "value" : "Assinado pedido de %1$(appName)@" } }, "ro" : { @@ -24171,7 +24273,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Cartões com chip" + "value" : "Smart Card" } }, "ro" : { @@ -24356,7 +24458,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Sem Nome" + "value" : "Sem nome" } }, "ro" : { @@ -24541,7 +24643,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Atualização Crítica de Segurança Requerida" + "value" : "Atualização Crítica de Segurança necessária" } }, "ro" : { @@ -24911,7 +25013,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Atualização Disponível" + "value" : "Atualização disponível" } }, "ro" : { @@ -25655,7 +25757,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Clique para Atualizar" + "value" : "Clique para atualizar" } }, "ro" : { @@ -26026,7 +26128,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Notas de Mudanças" + "value" : "Notas de Lançamento" } }, "ro" : { @@ -26211,7 +26313,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Versão de Teste" + "value" : "Build de Teste" } }, "ro" : { @@ -26766,7 +26868,7 @@ "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Baixar a última Nightly Build" + "value" : "Baixar a Nightly Build mais recente" } }, "ro" : { From 85ba5a1a376ebe44f215b354e64a9a4e33e158f3 Mon Sep 17 00:00:00 2001 From: Max Goedjen Date: Sun, 6 Sep 2026 20:46:51 -0700 Subject: [PATCH 2/5] Set Xcode 27 builder (#822) * Set Xcode 27 builder * Fix Xcode 27 build errors --- .github/workflows/codeql.yml | 9 ++------- .github/workflows/nightly.yml | 4 +--- .github/workflows/oneoff.yml | 4 +--- .github/workflows/release.yml | 8 ++------ .github/workflows/test.yml | 4 +--- Sources/Packages/Sources/Brief/Updater.swift | 6 +++--- .../Sources/SecretAgentKit/SocketController.swift | 6 +++--- Sources/SecretAgent/AppDelegate.swift | 4 ++-- Sources/Secretive/App.swift | 2 +- Sources/Secretive/Views/Views/AgentStatusView.swift | 2 +- 10 files changed, 17 insertions(+), 32 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index bf95a16..fb0f31a 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -11,7 +11,7 @@ on: jobs: analyze: name: Analyze (${{ matrix.language }}) - runs-on: ${{ (matrix.language == 'swift' && 'macos-26') || 'ubuntu-latest' }} + runs-on: ${{ (matrix.language == 'swift' && 'xcode-27') || 'ubuntu-latest' }} permissions: security-events: write packages: read @@ -24,20 +24,15 @@ jobs: include: - language: actions build-mode: none - # Disable this until CodeQL supports Xcode 26 builds. - # - language: swift - # build-mode: manual steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} - if: matrix.build-mode == 'manual' - name: "Select Xcode" - run: sudo xcrun xcode-select -s /Applications/Xcode_26.4.app - if: matrix.build-mode == 'manual' name: "Build" run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme Secretive CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 112f5fb..aea9a4d 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -6,7 +6,7 @@ on: jobs: build: - runs-on: macos-26 + runs-on: xcode-27 permissions: id-token: write contents: write @@ -25,8 +25,6 @@ jobs: APPLE_API_KEY_DATA: ${{ secrets.APPLE_API_KEY_DATA }} APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} run: ./.github/scripts/signing.sh - - name: Set Environment - run: sudo xcrun xcode-select -s /Applications/Xcode_26.4.app - name: Update Build Number env: RUN_ID: ${{ github.run_id }} diff --git a/.github/workflows/oneoff.yml b/.github/workflows/oneoff.yml index 9b1dbcf..e67b021 100644 --- a/.github/workflows/oneoff.yml +++ b/.github/workflows/oneoff.yml @@ -5,7 +5,7 @@ on: jobs: build: - runs-on: macos-26 + runs-on: xcode-27 permissions: id-token: write contents: write @@ -24,8 +24,6 @@ jobs: APPLE_API_KEY_DATA: ${{ secrets.APPLE_API_KEY_DATA }} APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} run: ./.github/scripts/signing.sh - - name: Set Environment - run: sudo xcrun xcode-select -s /Applications/Xcode_26.4.app - name: Update Build Number env: RUN_ID: ${{ github.run_id }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a88efe6..8a811de 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,7 +8,7 @@ jobs: test: permissions: contents: read - runs-on: macos-26 + runs-on: xcode-27 timeout-minutes: 10 steps: - uses: actions/checkout@v5 @@ -21,8 +21,6 @@ jobs: APPLE_API_KEY_DATA: ${{ secrets.APPLE_API_KEY_DATA }} APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} run: ./.github/scripts/signing.sh - - name: Set Environment - run: sudo xcrun xcode-select -s /Applications/Xcode_26.4.app - name: Test run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme PackageTests test # SPM doesn't seem to pick up on the tests currently? @@ -34,7 +32,7 @@ jobs: attestations: write artifact-metadata: write actions: read - runs-on: macos-26 + runs-on: xcode-27 timeout-minutes: 10 steps: - uses: actions/checkout@v5 @@ -47,8 +45,6 @@ jobs: APPLE_API_KEY_DATA: ${{ secrets.APPLE_API_KEY_DATA }} APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} run: ./.github/scripts/signing.sh - - name: Set Environment - run: sudo xcrun xcode-select -s /Applications/Xcode_26.4.app - name: Update Build Number env: TAG_NAME: ${{ github.ref }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8ec1d21..3866087 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,12 +5,10 @@ jobs: test: permissions: contents: read - runs-on: macos-26 + runs-on: xcode-27 timeout-minutes: 10 steps: - uses: actions/checkout@v5 - - name: Set Environment - run: sudo xcrun xcode-select -s /Applications/Xcode_26.4.app - name: Test Main Packages run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme PackageTests test # SPM doesn't seem to pick up on the tests currently? diff --git a/Sources/Packages/Sources/Brief/Updater.swift b/Sources/Packages/Sources/Brief/Updater.swift index 12be1ee..cbb41fa 100644 --- a/Sources/Packages/Sources/Brief/Updater.swift +++ b/Sources/Packages/Sources/Brief/Updater.swift @@ -34,12 +34,12 @@ import XPCWrappers ) { self.osVersion = osVersion self.currentVersion = currentVersion - Task { + _ = Task { if checkOnLaunch { try await checkForUpdates() } - while !Task.isCancelled { - try? await Task.sleep(for: .seconds(Int(checkFrequency))) + while true { + try await Task.sleep(for: .seconds(Int(checkFrequency))) try await checkForUpdates() } } diff --git a/Sources/Packages/Sources/SecretAgentKit/SocketController.swift b/Sources/Packages/Sources/SecretAgentKit/SocketController.swift index 7839037..43d699f 100644 --- a/Sources/Packages/Sources/SecretAgentKit/SocketController.swift +++ b/Sources/Packages/Sources/SecretAgentKit/SocketController.swift @@ -94,7 +94,7 @@ extension SocketController { guard !data.isEmpty else { logger.debug("Socket controller received empty data, ending continuation.") messagesContinuation.finish() - try fileHandle.close() + try? fileHandle.close() return } messagesContinuation.yield(data) @@ -126,8 +126,8 @@ private extension SocketPort { convenience init(path: String) { var addr = sockaddr_un() - let length = unsafe withUnsafeMutablePointer(to: &addr.sun_path.0) { pointer in - unsafe path.withCString { cstring in + let length = withUnsafeMutablePointer(to: &addr.sun_path.0) { pointer in + path.withCString { cstring in let len = unsafe strlen(cstring) unsafe strncpy(pointer, cstring, len) return len diff --git a/Sources/SecretAgent/AppDelegate.swift b/Sources/SecretAgent/AppDelegate.swift index a3a7507..ce8d06e 100644 --- a/Sources/SecretAgent/AppDelegate.swift +++ b/Sources/SecretAgent/AppDelegate.swift @@ -52,15 +52,15 @@ class AppDelegate: NSObject, NSApplicationDelegate { Task { for await session in socketController.sessions { Task { - let inputParser = try await XPCAgentInputParser() do { + let inputParser = try await XPCAgentInputParser() for await message in session.messages { let request = try await inputParser.parse(data: message) let agentResponse = await agent.handle(request: request, provenance: session.provenance) try session.write(agentResponse) } } catch { - try session.close() + try? session.close() } } } diff --git a/Sources/Secretive/App.swift b/Sources/Secretive/App.swift index fa405d9..202b380 100644 --- a/Sources/Secretive/App.swift +++ b/Sources/Secretive/App.swift @@ -25,7 +25,7 @@ struct Secretive: App { guard !agentLaunchController.developmentBuild else { return } if justUpdatedChecker.justUpdatedBuild || !agentLaunchController.running { // Relaunch the agent, since it'll be running from earlier update still - try await agentLaunchController.forceLaunch() + try? await agentLaunchController.forceLaunch() } } } diff --git a/Sources/Secretive/Views/Views/AgentStatusView.swift b/Sources/Secretive/Views/Views/AgentStatusView.swift index 43c69a1..2313b01 100644 --- a/Sources/Secretive/Views/Views/AgentStatusView.swift +++ b/Sources/Secretive/Views/Views/AgentStatusView.swift @@ -101,7 +101,7 @@ struct AgentNotRunningView: View { guard !loading else { return } loading = true Task { - try await agentLaunchController.forceLaunch() + try? await agentLaunchController.forceLaunch() loading = false if !agentLaunchController.running { From 8577e7cbc306e19ba8fc8f493426193ea5b1ff35 Mon Sep 17 00:00:00 2001 From: Max Goedjen Date: Sun, 6 Sep 2026 21:08:42 -0700 Subject: [PATCH 3/5] Parsing OpenSSH extensions (#823) * Agent extension parsing * Temporarily disable binding * Fix test * TODO Cleanup * TODO Cleanup * Put back assertion --- .../OpenSSHPublicKeyWriter.swift | 2 +- .../SSHProtocolKit/OpenSSHReader.swift | 13 ++ .../OpenSSHSignatureWriter.swift | 2 +- .../SSHProtocolKit/SSHAgentInputParser.swift | 149 +++++++++++++++++- .../SSHProtocolKit/SSHAgentProtocol.swift | 76 ++++++++- .../SSHProtocolExtensions.swift | 76 +++++++++ .../Sources/SecretAgentKit/Agent.swift | 56 ++++++- .../Types/SigningRequestTarget.swift | 52 ++++++ .../SecretAgentKitTests/AgentTests.swift | 2 +- Sources/SecretAgent/SecretAgent.entitlements | 4 +- Sources/Secretive.xcodeproj/project.pbxproj | 28 +++- .../xcshareddata/xcschemes/Secretive.xcscheme | 3 + 12 files changed, 444 insertions(+), 19 deletions(-) create mode 100644 Sources/Packages/Sources/SSHProtocolKit/SSHProtocolExtensions.swift create mode 100644 Sources/Packages/Sources/SecretKit/Types/SigningRequestTarget.swift diff --git a/Sources/Packages/Sources/SSHProtocolKit/OpenSSHPublicKeyWriter.swift b/Sources/Packages/Sources/SSHProtocolKit/OpenSSHPublicKeyWriter.swift index 16854a3..cbc105b 100644 --- a/Sources/Packages/Sources/SSHProtocolKit/OpenSSHPublicKeyWriter.swift +++ b/Sources/Packages/Sources/SSHProtocolKit/OpenSSHPublicKeyWriter.swift @@ -19,7 +19,7 @@ public struct OpenSSHPublicKeyWriter: Sendable { ("nistp" + String(describing: secret.keyType.size)).lengthAndData + secret.publicKey.lengthAndData case .mldsa: - // https://www.ietf.org/archive/id/draft-sfluhrer-ssh-mldsa-04.txt + // https://datatracker.ietf.org/doc/html/draft-sfluhrer-ssh-mldsa-05 openSSHIdentifier(for: secret.keyType).lengthAndData + secret.publicKey.lengthAndData case .rsa: diff --git a/Sources/Packages/Sources/SSHProtocolKit/OpenSSHReader.swift b/Sources/Packages/Sources/SSHProtocolKit/OpenSSHReader.swift index e33d738..bdcc1a9 100644 --- a/Sources/Packages/Sources/SSHProtocolKit/OpenSSHReader.swift +++ b/Sources/Packages/Sources/SSHProtocolKit/OpenSSHReader.swift @@ -42,6 +42,18 @@ public final class OpenSSHReader { return convertEndianness ? T(value.bigEndian) : T(value) } + public func readNextByteAsBool() throws(OpenSSHReaderError) -> Bool { + let size = MemoryLayout.size + guard remaining.count >= size else { throw .beyondBounds } + let lengthRange = 0.. String { try String(decoding: readNextChunk(convertEndianness: convertEndianness), as: UTF8.self) } @@ -53,5 +65,6 @@ public final class OpenSSHReader { } public enum OpenSSHReaderError: Error, Codable { + case incorrectFormat case beyondBounds } diff --git a/Sources/Packages/Sources/SSHProtocolKit/OpenSSHSignatureWriter.swift b/Sources/Packages/Sources/SSHProtocolKit/OpenSSHSignatureWriter.swift index 25397db..8bdb939 100644 --- a/Sources/Packages/Sources/SSHProtocolKit/OpenSSHSignatureWriter.swift +++ b/Sources/Packages/Sources/SSHProtocolKit/OpenSSHSignatureWriter.swift @@ -17,7 +17,7 @@ public struct OpenSSHSignatureWriter: Sendable { // https://datatracker.ietf.org/doc/html/rfc5656#section-3.1 ecdsaSignature(signature, keyType: secret.keyType) case .mldsa: - // https://datatracker.ietf.org/doc/html/draft-sfluhrer-ssh-mldsa-00#name-public-key-algorithms + // https://datatracker.ietf.org/doc/html/draft-sfluhrer-ssh-mldsa-05 mldsaSignature(signature, keyType: secret.keyType) case .rsa: // https://datatracker.ietf.org/doc/html/rfc4253#section-6.6 diff --git a/Sources/Packages/Sources/SSHProtocolKit/SSHAgentInputParser.swift b/Sources/Packages/Sources/SSHProtocolKit/SSHAgentInputParser.swift index d219a35..c547aaa 100644 --- a/Sources/Packages/Sources/SSHProtocolKit/SSHAgentInputParser.swift +++ b/Sources/Packages/Sources/SSHProtocolKit/SSHAgentInputParser.swift @@ -3,6 +3,8 @@ import OSLog import SecretKit import CertificateKit +import CryptoKit + public protocol SSHAgentInputParserProtocol { func parse(data: Data) async throws -> SSHAgent.Request @@ -53,8 +55,8 @@ public struct SSHAgentInputParser: SSHAgentInputParserProtocol { return .unlock case SSHAgent.Request.addSmartcardKeyConstrained.protocolID: return .addSmartcardKeyConstrained - case SSHAgent.Request.protocolExtension.protocolID: - return .protocolExtension + case SSHAgent.Request.protocolExtension(.empty).protocolID: + return .protocolExtension(try protocolExtension(from: body)) default: return .unknown(rawRequestInt) } @@ -64,12 +66,150 @@ public struct SSHAgentInputParser: SSHAgentInputParserProtocol { extension SSHAgentInputParser { + private enum Constants { + static let userAuthMagic: UInt8 = 50 // SSH2_MSG_USERAUTH_REQUEST + static let sshSigMagic = Data("SSHSIG".utf8) + } + func signatureRequestContext(from data: Data) throws(OpenSSHReaderError) -> SSHAgent.Request.SignatureRequestContext { let reader = OpenSSHReader(data: data) let rawKeyBlob = try reader.readNextChunk() let keyBlob = certificatePublicKeyBlob(from: rawKeyBlob) ?? rawKeyBlob - let dataToSign = try reader.readNextChunk() - return SSHAgent.Request.SignatureRequestContext(keyBlob: keyBlob, dataToSign: dataToSign) + let rawPayload = try reader.readNextChunk() + let payload: SSHAgent.Request.SignatureRequestContext.SignaturePayload + do { + if rawPayload.count > 6 && rawPayload[0..<6] == Constants.sshSigMagic { + payload = .init(raw: rawPayload, decoded: .sshSig(try sshSigPayload(from: rawPayload[6...]))) + } else { + payload = .init(raw: rawPayload, decoded: .sshConnection(try sshConnectionPayload(from: rawPayload))) + } + } catch { + payload = .init(raw: rawPayload, decoded: nil) + } + return SSHAgent.Request.SignatureRequestContext(keyBlob: keyBlob, dataToSign: payload) + } + + func sshSigPayload(from data: Data) throws(OpenSSHReaderError) -> SSHAgent.Request.SignatureRequestContext.SignaturePayload.DecodedPayload.SSHSigPayload { + // https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.sshsig#L79 + let payloadReader = OpenSSHReader(data: data) + let namespace = try payloadReader.readNextChunkAsString() + _ = try payloadReader.readNextChunk() // reserved + let hashAlgorithm = try payloadReader.readNextChunkAsString() + let hash = try payloadReader.readNextChunk() + return .init( + namespace: namespace, + hashAlgorithm: hashAlgorithm, + hash: hash + ) + } + + func sshConnectionPayload(from data: Data) throws(OpenSSHReaderError) -> SSHAgent.Request.SignatureRequestContext.SignaturePayload.DecodedPayload.SSHConnectionPayload { + let payloadReader = OpenSSHReader(data: data) + _ = try payloadReader.readNextChunk() + let magic = try payloadReader.readNextBytes(as: UInt8.self, convertEndianness: false) + guard magic == Constants.userAuthMagic else { throw .incorrectFormat } + let username = try payloadReader.readNextChunkAsString() + _ = try payloadReader.readNextChunkAsString() // "ssh-connection" + _ = try payloadReader.readNextChunkAsString() // "publickey-hostbound-v00@openssh.com" + let hasSignature = try payloadReader.readNextByteAsBool() + let algorithm = try payloadReader.readNextChunkAsString() + let publicKeyReader = try payloadReader.readNextChunkAsSubReader() + _ = try publicKeyReader.readNextChunk() + _ = try publicKeyReader.readNextChunk() + let publicKey = try publicKeyReader.readNextChunk() + let hostKeyReader = try payloadReader.readNextChunkAsSubReader() + _ = try hostKeyReader.readNextChunk() + let hostKey = try hostKeyReader.readNextChunk() + return .init( + username: username, + hasSignature: hasSignature, + publicKeyAlgorithm: algorithm, + publicKey: publicKey, + hostKey: hostKey, + ) + } + + func protocolExtension(from data: Data) throws(AgentParsingError) -> SSHAgent.ProtocolExtension { + do { + let reader = OpenSSHReader(data: data) + let nameRaw = try reader.readNextChunkAsString() + let nameSplit = nameRaw.split(separator: "@") + guard nameSplit.count == 2 else { + throw AgentParsingError.invalidData + } + let (name, domain) = (nameSplit[0], nameSplit[1]) + switch domain { + case SSHAgent.ProtocolExtension.OpenSSHExtension.domain: + switch name { + case SSHAgent.ProtocolExtension.OpenSSHExtension.sessionBind(.empty).name: + let hostkeyBlob = try reader.readNextChunkAsSubReader() + let hostKeyType = try hostkeyBlob.readNextChunkAsString() + let hostKeyData = try hostkeyBlob.readNextChunk() + let sessionID = try reader.readNextChunk() + let signatureBlob = try reader.readNextChunkAsSubReader() + _ = try signatureBlob.readNextChunk() // key type again + let signature = try signatureBlob.readNextChunk() + let forwarding = try reader.readNextByteAsBool() + switch hostKeyType { + case "ssh-ed25519": + let hostKey = try CryptoKit.Curve25519.Signing.PublicKey(rawRepresentation: hostKeyData) + guard hostKey.isValidSignature(signature, for: sessionID) else { + throw AgentParsingError.incorrectSignature + } + case "ecdsa-sha2-nistp256": + let hostKey = try CryptoKit.P256.Signing.PublicKey(rawRepresentation: hostKeyData) + guard hostKey.isValidSignature(try .init(rawRepresentation: signature), for: sessionID) else { + throw AgentParsingError.incorrectSignature + } + case "ecdsa-sha2-nistp384": + let hostKey = try CryptoKit.P384.Signing.PublicKey(rawRepresentation: hostKeyData) + guard hostKey.isValidSignature(try .init(rawRepresentation: signature), for: sessionID) else { + throw AgentParsingError.incorrectSignature + } + case "ssh-mldsa-65": + if #available(macOS 26.0, *) { + let hostKey = try CryptoKit.MLDSA65.PublicKey(rawRepresentation: hostKeyData) + guard hostKey.isValidSignature(signature, for: sessionID) else { + throw AgentParsingError.incorrectSignature + } + } else { + throw AgentParsingError.unhandledRequest + } + case "ssh-mldsa-87": + if #available(macOS 26.0, *) { + let hostKey = try CryptoKit.MLDSA65.PublicKey(rawRepresentation: hostKeyData) + guard hostKey.isValidSignature(signature, for: sessionID) else { + throw AgentParsingError.incorrectSignature + } + } else { + throw AgentParsingError.unhandledRequest + } + case "ssh-rsa": + throw AgentParsingError.unhandledRequest + default: + throw AgentParsingError.unhandledRequest + } + let context = SSHAgent.ProtocolExtension.OpenSSHExtension.SessionBindContext( + hostKey: hostKeyData, + sessionID: sessionID, + signature: signature, + forwarding: forwarding + ) + return .openSSH(.sessionBind(context)) + default: + return .openSSH(.unknown(String(name))) + } + default: + return .unknown(nameRaw) + } + + } catch let error as OpenSSHReaderError { + throw .openSSHReader(error) + } catch let error as AgentParsingError { + throw error + } catch { + throw .unknownRequest + } } func certificatePublicKeyBlob(from hash: Data) -> Data? { @@ -99,6 +239,7 @@ extension SSHAgentInputParser { case unknownRequest case unhandledRequest case invalidData + case incorrectSignature case openSSHReader(OpenSSHReaderError) } diff --git a/Sources/Packages/Sources/SSHProtocolKit/SSHAgentProtocol.swift b/Sources/Packages/Sources/SSHProtocolKit/SSHAgentProtocol.swift index 0007989..1f1a36f 100644 --- a/Sources/Packages/Sources/SSHProtocolKit/SSHAgentProtocol.swift +++ b/Sources/Packages/Sources/SSHProtocolKit/SSHAgentProtocol.swift @@ -19,7 +19,7 @@ extension SSHAgent { case lock case unlock case addSmartcardKeyConstrained - case protocolExtension + case protocolExtension(ProtocolExtension) case unknown(UInt8) public var protocolID: UInt8 { @@ -60,18 +60,82 @@ extension SSHAgent { public struct SignatureRequestContext: Sendable, Codable { public let keyBlob: Data - public let dataToSign: Data + public let dataToSign: SignaturePayload - public init(keyBlob: Data, dataToSign: Data) { + public init(keyBlob: Data, dataToSign: SignaturePayload) { self.keyBlob = keyBlob self.dataToSign = dataToSign } public static var empty: SignatureRequestContext { - SignatureRequestContext(keyBlob: Data(), dataToSign: Data()) + SignatureRequestContext(keyBlob: Data(), dataToSign: SignaturePayload(raw: Data(), decoded: nil)) + } + + public struct SignaturePayload: Sendable, Codable { + + public let raw: Data + public let decoded: DecodedPayload? + + public init( + raw: Data, + decoded: DecodedPayload? + ) { + self.raw = raw + self.decoded = decoded + } + + public enum DecodedPayload: Sendable, Codable { + case sshConnection(SSHConnectionPayload) + case sshSig(SSHSigPayload) + + public struct SSHConnectionPayload: Sendable, Codable { + + public let username: String + public let hasSignature: Bool + public let publicKeyAlgorithm: String + public let publicKey: Data + public let hostKey: Data + + public init( + username: String, + hasSignature: Bool, + publicKeyAlgorithm: String, + publicKey: Data, + hostKey: Data + ) { + self.username = username + self.hasSignature = hasSignature + self.publicKeyAlgorithm = publicKeyAlgorithm + self.publicKey = publicKey + self.hostKey = hostKey + } + + } + + public struct SSHSigPayload: Sendable, Codable { + + public let namespace: String + public let hashAlgorithm: String + public let hash: Data + + public init( + namespace: String, + hashAlgorithm: String, + hash: Data, + ) { + self.namespace = namespace + self.hashAlgorithm = hashAlgorithm + self.hash = hash + } + + } + + } + } } + } /// The type of the SSH Agent Response, as described in https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent#section-5.1 @@ -88,8 +152,8 @@ extension SSHAgent { switch self { case .agentFailure: "SSH_AGENT_FAILURE" case .agentSuccess: "SSH_AGENT_SUCCESS" - case .agentIdentitiesAnswer: "SSH_AGENT_IDENTITIES_ANSWER" - case .agentSignResponse: "SSH_AGENT_SIGN_RESPONSE" + case .agentIdentitiesAnswer: "SSH2_AGENT_IDENTITIES_ANSWER" + case .agentSignResponse: "SSH2_AGENT_SIGN_RESPONSE" case .agentExtensionFailure: "SSH_AGENT_EXTENSION_FAILURE" case .agentExtensionResponse: "SSH_AGENT_EXTENSION_RESPONSE" } diff --git a/Sources/Packages/Sources/SSHProtocolKit/SSHProtocolExtensions.swift b/Sources/Packages/Sources/SSHProtocolKit/SSHProtocolExtensions.swift new file mode 100644 index 0000000..511926f --- /dev/null +++ b/Sources/Packages/Sources/SSHProtocolKit/SSHProtocolExtensions.swift @@ -0,0 +1,76 @@ +import Foundation + +// Extensions, as defined in https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.agent + +extension SSHAgent { + + public enum ProtocolExtension: CustomDebugStringConvertible, Codable, Sendable { + case openSSH(OpenSSHExtension) + case unknown(String) + + public var debugDescription: String { + switch self { + case let .openSSH(protocolExtension): + protocolExtension.debugDescription + case .unknown(let string): + "Unknown (\(string))" + } + } + + public static var empty: ProtocolExtension { + .unknown("empty") + } + + private struct ProtocolExtensionParsingError: Error {} + + } + +} + +extension SSHAgent.ProtocolExtension { + + public enum OpenSSHExtension: CustomDebugStringConvertible, Codable, Sendable { + case sessionBind(SessionBindContext) + case unknown(String) + + public static var domain: String { + "openssh.com" + } + + public var name: String { + switch self { + case .sessionBind: + "session-bind" + case .unknown(let name): + name + } + } + + public var debugDescription: String { + "\(name)@\(OpenSSHExtension.domain)" + } + } + +} + +extension SSHAgent.ProtocolExtension.OpenSSHExtension { + + public struct SessionBindContext: Codable, Sendable { + + public let hostKey: Data + public let sessionID: Data + public let signature: Data + public let forwarding: Bool + + public init(hostKey: Data, sessionID: Data, signature: Data, forwarding: Bool) { + self.hostKey = hostKey + self.sessionID = sessionID + self.signature = signature + self.forwarding = forwarding + } + + public static let empty = SessionBindContext(hostKey: Data(), sessionID: Data(), signature: Data(), forwarding: false) + + } + +} diff --git a/Sources/Packages/Sources/SecretAgentKit/Agent.swift b/Sources/Packages/Sources/SecretAgentKit/Agent.swift index d3445ef..6700370 100644 --- a/Sources/Packages/Sources/SecretAgentKit/Agent.swift +++ b/Sources/Packages/Sources/SecretAgentKit/Agent.swift @@ -16,6 +16,8 @@ public final class Agent: Sendable { private let signatureWriter = OpenSSHSignatureWriter() private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "Agent") + @MainActor private var sessionID: SSHAgent.ProtocolExtension.OpenSSHExtension.SessionBindContext? + /// Initializes an agent with a store list and a witness. /// - Parameters: /// - storeList: The `SecretStoreList` to make available. @@ -31,7 +33,11 @@ public final class Agent: Sendable { extension Agent { - public func handle(request: SSHAgent.Request, provenance: SigningRequestProvenance) async -> Data { + public func handle( + request: SSHAgent.Request, + provenance: SigningRequestProvenance + ) async -> Data { + logger.debug("Agent received request of type \(request.debugDescription)") // Depending on the launch context (such as after macOS update), the agent may need to reload secrets before acting await reloadSecretsIfNeccessary() var response = Data() @@ -42,9 +48,54 @@ extension Agent { response.append(await identities()) logger.debug("Agent returned \(SSHAgent.Response.agentIdentitiesAnswer.debugDescription)") case .signRequest(let context): + let target: SigningRequestTarget? + switch context.dataToSign.decoded { + case .sshConnection(let payload): + target = .connection( + .init( + username: payload.username, + hasSignature: payload.hasSignature, + publicKeyAlgorithm: payload.publicKeyAlgorithm, + publicKey: payload.publicKey, + hostKey: payload.hostKey + ) + ) + if let boundSession = await sessionID { + guard payload.hostKey == boundSession.hostKey else { + logger.error("Agent received bind request, but host key does not match signature request host key.") + throw BindingFailure() + } + } + case .sshSig(let payload): + target = .signature( + .init( + namespace: payload.namespace, + hashAlgorithm: payload.hashAlgorithm, + hash: payload.hash + ) + ) + default: + target = nil + } + _ = target response.append(SSHAgent.Response.agentSignResponse.data) - response.append(try await sign(data: context.dataToSign, keyBlob: context.keyBlob, provenance: provenance)) + response.append(try await sign(data: context.dataToSign.raw, keyBlob: context.keyBlob, provenance: provenance)) logger.debug("Agent returned \(SSHAgent.Response.agentSignResponse.debugDescription)") + case .protocolExtension(.openSSH(.sessionBind(let bind))): + // This is disabled until forward enforcement is handled. + _ = bind + response = try await MainActor.run { + logger.debug("Agent received bind request but not currently supported.") + throw UnhandledRequestError() +// guard sessionID == nil else { +// logger.error("Agent received bind request, but already bound.") +// throw BindingFailure() +// } +// logger.debug("Agent bound") +// sessionID = bind +// return SSHAgent.Response.agentSuccess.data + } + logger.debug("Agent returned \(SSHAgent.Response.agentSuccess.debugDescription)") case .unknown(let value): logger.error("Agent received unknown request of type \(value).") throw UnhandledRequestError() @@ -142,6 +193,7 @@ extension Agent { struct NoMatchingKeyError: Error {} struct UnhandledRequestError: Error {} + struct BindingFailure: Error {} } diff --git a/Sources/Packages/Sources/SecretKit/Types/SigningRequestTarget.swift b/Sources/Packages/Sources/SecretKit/Types/SigningRequestTarget.swift new file mode 100644 index 0000000..351a67d --- /dev/null +++ b/Sources/Packages/Sources/SecretKit/Types/SigningRequestTarget.swift @@ -0,0 +1,52 @@ +import Foundation +import AppKit + +/// Describes the target of the signature operation. +public enum SigningRequestTarget: Sendable { + + case connection(ConnectionPayload) + case signature(SignaturePayload) + + public struct ConnectionPayload: Sendable, Codable{ + + public let username: String + public let hasSignature: Bool + public let publicKeyAlgorithm: String + public let publicKey: Data + public let hostKey: Data + + public init( + username: String, + hasSignature: Bool, + publicKeyAlgorithm: String, + publicKey: Data, + hostKey: Data, + ) { + self.username = username + self.hasSignature = hasSignature + self.publicKeyAlgorithm = publicKeyAlgorithm + self.publicKey = publicKey + self.hostKey = hostKey + } + + } + + public struct SignaturePayload: Sendable, Codable { + + public let namespace: String + public let hashAlgorithm: String + public let hash: Data + + public init( + namespace: String, + hashAlgorithm: String, + hash: Data, + ) { + self.namespace = namespace + self.hashAlgorithm = hashAlgorithm + self.hash = hash + } + + } + +} diff --git a/Sources/Packages/Tests/SecretAgentKitTests/AgentTests.swift b/Sources/Packages/Tests/SecretAgentKitTests/AgentTests.swift index b8bd9c5..8942fb6 100644 --- a/Sources/Packages/Tests/SecretAgentKitTests/AgentTests.swift +++ b/Sources/Packages/Tests/SecretAgentKitTests/AgentTests.swift @@ -68,7 +68,7 @@ import CertificateKit let signature = try P256.Signing.ECDSASignature(rawRepresentation: rs) // Correct signature #expect(try P256.Signing.PublicKey(x963Representation: Constants.Secrets.ecdsa256Secret.publicKey) - .isValidSignature(signature, for: context.dataToSign)) + .isValidSignature(signature, for: context.dataToSign.raw)) } // MARK: Witness protocol diff --git a/Sources/SecretAgent/SecretAgent.entitlements b/Sources/SecretAgent/SecretAgent.entitlements index 35188de..28f4467 100644 --- a/Sources/SecretAgent/SecretAgent.entitlements +++ b/Sources/SecretAgent/SecretAgent.entitlements @@ -16,10 +16,10 @@ 1 com.apple.security.hardened-process.hardened-heap - com.apple.security.smartcard - com.apple.security.hardened-process.platform-restrictions-string 2 + com.apple.security.smartcard + keychain-access-groups $(AppIdentifierPrefix)com.maxgoedjen.Secretive diff --git a/Sources/Secretive.xcodeproj/project.pbxproj b/Sources/Secretive.xcodeproj/project.pbxproj index 54f5f08..2b6e30b 100644 --- a/Sources/Secretive.xcodeproj/project.pbxproj +++ b/Sources/Secretive.xcodeproj/project.pbxproj @@ -699,7 +699,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastSwiftUpdateCheck = 2650; + LastSwiftUpdateCheck = 2700; LastUpgradeCheck = 2640; ORGANIZATIONNAME = "Max Goedjen"; TargetAttributes = { @@ -744,8 +744,8 @@ 50617D7E23FCE48D0099B055 /* Secretive */, 50A3B78924026B7500D209EA /* SecretAgent */, 50692D112E6FDB880043C7BB /* SecretiveUpdater */, - 50692E4F2E6FF9D20043C7BB /* SecretAgentInputParser */, 50E205132FAAB81C00402380 /* SecretiveCertificateParser */, + 50692E4F2E6FF9D20043C7BB /* SecretAgentInputParser */, ); }; /* End PBXProject section */ @@ -1515,6 +1515,7 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + AUTOMATION_APPLE_EVENTS = NO; CODE_SIGN_ENTITLEMENTS = SecretAgent/SecretAgent.entitlements; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; @@ -1533,6 +1534,7 @@ ENABLE_RESOURCE_ACCESS_CAMERA = NO; ENABLE_RESOURCE_ACCESS_CONTACTS = NO; ENABLE_RESOURCE_ACCESS_LOCATION = NO; + ENABLE_RESOURCE_ACCESS_PHOTO_LIBRARY = NO; ENABLE_RESOURCE_ACCESS_PRINTING = NO; ENABLE_RESOURCE_ACCESS_USB = NO; INFOPLIST_FILE = SecretAgent/Info.plist; @@ -1544,6 +1546,12 @@ MARKETING_VERSION = 1; PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgent"; PRODUCT_NAME = "$(TARGET_NAME)"; + RUNTIME_EXCEPTION_ALLOW_DYLD_ENVIRONMENT_VARIABLES = NO; + RUNTIME_EXCEPTION_ALLOW_JIT = NO; + RUNTIME_EXCEPTION_ALLOW_UNSIGNED_EXECUTABLE_MEMORY = NO; + RUNTIME_EXCEPTION_DEBUGGING_TOOL = NO; + RUNTIME_EXCEPTION_DISABLE_EXECUTABLE_PAGE_PROTECTION = NO; + RUNTIME_EXCEPTION_DISABLE_LIBRARY_VALIDATION = NO; }; name = Test; }; @@ -1551,6 +1559,7 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + AUTOMATION_APPLE_EVENTS = NO; CODE_SIGN_ENTITLEMENTS = SecretAgent/SecretAgent.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; @@ -1570,6 +1579,7 @@ ENABLE_RESOURCE_ACCESS_CAMERA = NO; ENABLE_RESOURCE_ACCESS_CONTACTS = NO; ENABLE_RESOURCE_ACCESS_LOCATION = NO; + ENABLE_RESOURCE_ACCESS_PHOTO_LIBRARY = NO; ENABLE_RESOURCE_ACCESS_PRINTING = NO; ENABLE_RESOURCE_ACCESS_USB = NO; INFOPLIST_FILE = SecretAgent/Info.plist; @@ -1581,6 +1591,12 @@ MARKETING_VERSION = 1; PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgent"; PRODUCT_NAME = "$(TARGET_NAME)"; + RUNTIME_EXCEPTION_ALLOW_DYLD_ENVIRONMENT_VARIABLES = NO; + RUNTIME_EXCEPTION_ALLOW_JIT = NO; + RUNTIME_EXCEPTION_ALLOW_UNSIGNED_EXECUTABLE_MEMORY = NO; + RUNTIME_EXCEPTION_DEBUGGING_TOOL = NO; + RUNTIME_EXCEPTION_DISABLE_EXECUTABLE_PAGE_PROTECTION = NO; + RUNTIME_EXCEPTION_DISABLE_LIBRARY_VALIDATION = NO; }; name = Debug; }; @@ -1588,6 +1604,7 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + AUTOMATION_APPLE_EVENTS = NO; CODE_SIGN_ENTITLEMENTS = SecretAgent/SecretAgent.entitlements; CODE_SIGN_IDENTITY = "Developer ID Application"; CODE_SIGN_STYLE = Manual; @@ -1608,6 +1625,7 @@ ENABLE_RESOURCE_ACCESS_CAMERA = NO; ENABLE_RESOURCE_ACCESS_CONTACTS = NO; ENABLE_RESOURCE_ACCESS_LOCATION = NO; + ENABLE_RESOURCE_ACCESS_PHOTO_LIBRARY = NO; ENABLE_RESOURCE_ACCESS_PRINTING = NO; ENABLE_RESOURCE_ACCESS_USB = NO; INFOPLIST_FILE = SecretAgent/Info.plist; @@ -1620,6 +1638,12 @@ PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgent"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "Secretive - Secret Agent"; + RUNTIME_EXCEPTION_ALLOW_DYLD_ENVIRONMENT_VARIABLES = NO; + RUNTIME_EXCEPTION_ALLOW_JIT = NO; + RUNTIME_EXCEPTION_ALLOW_UNSIGNED_EXECUTABLE_MEMORY = NO; + RUNTIME_EXCEPTION_DEBUGGING_TOOL = NO; + RUNTIME_EXCEPTION_DISABLE_EXECUTABLE_PAGE_PROTECTION = NO; + RUNTIME_EXCEPTION_DISABLE_LIBRARY_VALIDATION = NO; }; name = Release; }; diff --git a/Sources/Secretive.xcodeproj/xcshareddata/xcschemes/Secretive.xcscheme b/Sources/Secretive.xcodeproj/xcshareddata/xcschemes/Secretive.xcscheme index b7eccb7..77ddc38 100644 --- a/Sources/Secretive.xcodeproj/xcshareddata/xcschemes/Secretive.xcscheme +++ b/Sources/Secretive.xcodeproj/xcshareddata/xcschemes/Secretive.xcscheme @@ -87,6 +87,9 @@ ReferencedContainer = "container:Secretive.xcodeproj"> + + Date: Sun, 6 Sep 2026 21:13:34 -0700 Subject: [PATCH 4/5] Remove broken task entitlement lookup and only enforce on release builds. (#824) --- .../Packages/Sources/XPCWrappers/TeamID.swift | 26 ------------------- .../XPCWrappers/XPCServiceDelegate.swift | 4 ++- .../Sources/XPCWrappers/XPCTypedSession.swift | 4 ++- 3 files changed, 6 insertions(+), 28 deletions(-) delete mode 100644 Sources/Packages/Sources/XPCWrappers/TeamID.swift diff --git a/Sources/Packages/Sources/XPCWrappers/TeamID.swift b/Sources/Packages/Sources/XPCWrappers/TeamID.swift deleted file mode 100644 index 56848a1..0000000 --- a/Sources/Packages/Sources/XPCWrappers/TeamID.swift +++ /dev/null @@ -1,26 +0,0 @@ -import Foundation - -extension ProcessInfo { - private static let fallbackTeamID = "Z72PRUAWF6" - - private static let teamID: String = { - #if DEBUG - guard let task = SecTaskCreateFromSelf(nil) else { - assertionFailure("SecTaskCreateFromSelf failed") - return fallbackTeamID - } - - guard let value = SecTaskCopyValueForEntitlement(task, "com.apple.developer.team-identifier" as CFString, nil) as? String else { - assertionFailure("SecTaskCopyValueForEntitlement(com.apple.developer.team-identifier) failed") - return fallbackTeamID - } - - return value - #else - /// Always use hardcoded team ID for release builds, just in case. - return fallbackTeamID - #endif - }() - - public var teamID: String { Self.teamID } -} diff --git a/Sources/Packages/Sources/XPCWrappers/XPCServiceDelegate.swift b/Sources/Packages/Sources/XPCWrappers/XPCServiceDelegate.swift index b2050f4..3719135 100644 --- a/Sources/Packages/Sources/XPCWrappers/XPCServiceDelegate.swift +++ b/Sources/Packages/Sources/XPCWrappers/XPCServiceDelegate.swift @@ -12,7 +12,9 @@ public final class XPCServiceDelegate: NSObject, NSXPCListenerDelegate { newConnection.exportedInterface = NSXPCInterface(with: (any _XPCProtocol).self) let exportedObject = exportedObject newConnection.exportedObject = exportedObject - newConnection.setCodeSigningRequirement("anchor apple generic and certificate leaf[subject.OU] = \"\(ProcessInfo.processInfo.teamID)\"") +#if !DEBUG + newConnection.setCodeSigningRequirement("anchor apple generic and certificate leaf[subject.OU] = \"Z72PRUAWF6\"") +#endif newConnection.resume() return true } diff --git a/Sources/Packages/Sources/XPCWrappers/XPCTypedSession.swift b/Sources/Packages/Sources/XPCWrappers/XPCTypedSession.swift index 509c48e..101a21b 100644 --- a/Sources/Packages/Sources/XPCWrappers/XPCTypedSession.swift +++ b/Sources/Packages/Sources/XPCWrappers/XPCTypedSession.swift @@ -8,7 +8,9 @@ public struct XPCTypedSession Date: Sun, 6 Sep 2026 21:32:51 -0700 Subject: [PATCH 5/5] Add host lookup xpc service (#826) * Add host lookup xpc service * Remove restriction that snuck in * Test fixes --- .../Packages/Resources/Localizable.xcstrings | 746 +++++++++++++++++- .../Sources/SecretAgentKit/Agent.swift | 17 +- .../SecretAgentKit/SigningWitness.swift | 5 +- .../SecretKit/Erasers/AnySecretStore.swift | 8 +- .../Sources/SecretKit/Types/SecretStore.swift | 2 +- .../Types/SigningRequestTarget.swift | 3 + .../SecureEnclaveStore.swift | 11 +- .../SmartCardSecretKit/SmartCardStore.swift | 2 +- .../SecretAgentKitTests/AgentTests.swift | 18 +- .../Tests/SecretAgentKitTests/StubStore.swift | 2 +- .../SecretAgentKitTests/StubWitness.swift | 4 +- Sources/SecretAgent/AppDelegate.swift | 4 +- Sources/SecretAgent/Notifier.swift | 27 +- Sources/SecretAgent/XPCHostsfileReader.swift | 36 + Sources/SecretAgentHostsfileReader/Info.plist | 11 + .../SecretAgentHostsfileReader.entitlements | 18 + .../SecretAgentHostsfileReader.swift | 25 + Sources/SecretAgentHostsfileReader/main.swift | 7 + Sources/Secretive.xcodeproj/project.pbxproj | 210 +++++ .../Preview Content/PreviewStore.swift | 4 +- .../SecretiveCertificateParser.entitlements | 8 +- 21 files changed, 1126 insertions(+), 42 deletions(-) create mode 100644 Sources/SecretAgent/XPCHostsfileReader.swift create mode 100644 Sources/SecretAgentHostsfileReader/Info.plist create mode 100644 Sources/SecretAgentHostsfileReader/SecretAgentHostsfileReader.entitlements create mode 100644 Sources/SecretAgentHostsfileReader/SecretAgentHostsfileReader.swift create mode 100644 Sources/SecretAgentHostsfileReader/main.swift diff --git a/Sources/Packages/Resources/Localizable.xcstrings b/Sources/Packages/Resources/Localizable.xcstrings index 0ec7f75..2d9ad4f 100644 --- a/Sources/Packages/Resources/Localizable.xcstrings +++ b/Sources/Packages/Resources/Localizable.xcstrings @@ -5548,6 +5548,378 @@ } } }, + "auth_context_request_signature_for_connection_description" : { + "comment" : "When the user performs a signature action using a secret, they are shown a prompt to approve the action. This is the description, showing which secret will be used, and where the request is coming from. The first placeholder is the name of the app requesting the operation. The second placeholder is the name of the secret.", + "extractionState" : "manual", + "localizations" : { + "af" : { + "stringUnit" : { + "state" : "new", + "value" : "sign a request from “%1$(appName)@” using secret “%2$(targetName)@”" + } + }, + "ar" : { + "stringUnit" : { + "state" : "new", + "value" : "sign a request from “%1$(appName)@” using secret “%2$(targetName)@”" + } + }, + "ca" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "signa una petición de “%1$(appName)@” usant el secret “%2$(targetName)@”" + } + }, + "cs" : { + "stringUnit" : { + "state" : "new", + "value" : "sign a request from “%1$(appName)@” using secret “%2$(targetName)@”" + } + }, + "da" : { + "stringUnit" : { + "state" : "new", + "value" : "sign a request from “%1$(appName)@” using secret “%2$(targetName)@”" + } + }, + "de" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "eine Anfrage von “%1$(appName)@” mit dem Secret “%2$(targetName)@” signieren" + } + }, + "el" : { + "stringUnit" : { + "state" : "new", + "value" : "sign a request from “%1$(appName)@” using secret “%2$(targetName)@”" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "sign a request from “%1$(appName)@” to connect to “%2$(targetName)@” using secret “%3$(secretName)@”" + } + }, + "es" : { + "stringUnit" : { + "state" : "new", + "value" : "sign a request from “%1$(appName)@” using secret “%2$(targetName)@”" + } + }, + "fi" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "allekirjoita pyyntö lähteestä “%1$(appName)@” käyttäen salaisuutta “%2$(targetName)@”" + } + }, + "fr" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "signer une requête de “%1$(appName)@” en utilisant le secret “%2$(targetName)@”" + } + }, + "he" : { + "stringUnit" : { + "state" : "new", + "value" : "sign a request from “%1$(appName)@” using secret “%2$(targetName)@”" + } + }, + "hu" : { + "stringUnit" : { + "state" : "new", + "value" : "sign a request from “%1$(appName)@” using secret “%2$(targetName)@”" + } + }, + "it" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "firma la richiesta di “%1$(appName)@” usando il Segreto “%2$(targetName)@”" + } + }, + "ja" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "シークレット“%2$(targetName)@”を使って“%1$(appName)@”の要求に署名します" + } + }, + "ko" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "비밀 “%2$(targetName)@”를 사용해서 “%1$(appName)@”의 요청에 서명" + } + }, + "nb" : { + "stringUnit" : { + "state" : "new", + "value" : "sign a request from “%1$(appName)@” using secret “%2$(targetName)@”" + } + }, + "nl" : { + "stringUnit" : { + "state" : "new", + "value" : "sign a request from “%1$(appName)@” using secret “%2$(targetName)@”" + } + }, + "pl" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "podpisz zapytanie od “%1$(appName)@” za pomocą sekretu “%2$(targetName)@”" + } + }, + "pt" : { + "stringUnit" : { + "state" : "new", + "value" : "sign a request from “%1$(appName)@” using secret “%2$(targetName)@”" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "assinar requisição a partir do “%1$(appName)@” utilizando o segredo “%2$(targetName)@”" + } + }, + "ro" : { + "stringUnit" : { + "state" : "new", + "value" : "sign a request from “%1$(appName)@” using secret “%2$(targetName)@”" + } + }, + "ru" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "подписать запрос от “%1$(appName)@” используя секрет “%2$(targetName)@”" + } + }, + "sr" : { + "stringUnit" : { + "state" : "new", + "value" : "sign a request from “%1$(appName)@” using secret “%2$(targetName)@”" + } + }, + "sv" : { + "stringUnit" : { + "state" : "new", + "value" : "sign a request from “%1$(appName)@” using secret “%2$(targetName)@”" + } + }, + "tr" : { + "stringUnit" : { + "state" : "new", + "value" : "sign a request from “%1$(appName)@” using secret “%2$(targetName)@”" + } + }, + "uk" : { + "stringUnit" : { + "state" : "new", + "value" : "sign a request from “%1$(appName)@” using secret “%2$(targetName)@”" + } + }, + "vi" : { + "stringUnit" : { + "state" : "new", + "value" : "sign a request from “%1$(appName)@” using secret “%2$(targetName)@”" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "用密钥串“%2$(targetName)@”认证来自“%1$(appName)@”的请求" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "用Secret「%2$(targetName)@」認證来自「%1$(appName)@」的请求" + } + } + } + }, + "auth_context_request_signature_for_signature_description" : { + "comment" : "When the user performs a signature action using a secret, they are shown a prompt to approve the action. This is the description, showing which secret will be used, and where the request is coming from. The first placeholder is the name of the app requesting the operation. The second placeholder is the name of the secret.", + "extractionState" : "manual", + "localizations" : { + "af" : { + "stringUnit" : { + "state" : "new", + "value" : "sign a request from “%1$(namespace)@” using secret “%2$(appName)@”" + } + }, + "ar" : { + "stringUnit" : { + "state" : "new", + "value" : "sign a request from “%1$(namespace)@” using secret “%2$(appName)@”" + } + }, + "ca" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "signa una petición de “%1$(namespace)@” usant el secret “%2$(appName)@”" + } + }, + "cs" : { + "stringUnit" : { + "state" : "new", + "value" : "sign a request from “%1$(namespace)@” using secret “%2$(appName)@”" + } + }, + "da" : { + "stringUnit" : { + "state" : "new", + "value" : "sign a request from “%1$(namespace)@” using secret “%2$(appName)@”" + } + }, + "de" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "eine Anfrage von “%1$(namespace)@” mit dem Secret “%2$(appName)@” signieren" + } + }, + "el" : { + "stringUnit" : { + "state" : "new", + "value" : "sign a request from “%1$(namespace)@” using secret “%2$(appName)@”" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "sign a %1$(namespace)@“ request from “%2$(appName)@” using secret “%3$(secretName)@”" + } + }, + "es" : { + "stringUnit" : { + "state" : "new", + "value" : "sign a request from “%1$(namespace)@” using secret “%2$(appName)@”" + } + }, + "fi" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "allekirjoita pyyntö lähteestä “%1$(namespace)@” käyttäen salaisuutta “%2$(appName)@”" + } + }, + "fr" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "signer une requête de “%1$(namespace)@” en utilisant le secret “%2$(appName)@”" + } + }, + "he" : { + "stringUnit" : { + "state" : "new", + "value" : "sign a request from “%1$(namespace)@” using secret “%2$(appName)@”" + } + }, + "hu" : { + "stringUnit" : { + "state" : "new", + "value" : "sign a request from “%1$(namespace)@” using secret “%2$(appName)@”" + } + }, + "it" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "firma la richiesta di “%1$(namespace)@” usando il Segreto “%2$(appName)@”" + } + }, + "ja" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "シークレット“%2$(appName)@”を使って“%1$(namespace)@”の要求に署名します" + } + }, + "ko" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "비밀 “%2$(appName)@”를 사용해서 “%1$(namespace)@”의 요청에 서명" + } + }, + "nb" : { + "stringUnit" : { + "state" : "new", + "value" : "sign a request from “%1$(namespace)@” using secret “%2$(appName)@”" + } + }, + "nl" : { + "stringUnit" : { + "state" : "new", + "value" : "sign a request from “%1$(namespace)@” using secret “%2$(appName)@”" + } + }, + "pl" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "podpisz zapytanie od “%1$(namespace)@” za pomocą sekretu “%2$(appName)@”" + } + }, + "pt" : { + "stringUnit" : { + "state" : "new", + "value" : "sign a request from “%1$(namespace)@” using secret “%2$(appName)@”" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "assinar requisição a partir do “%1$(namespace)@” utilizando o segredo “%2$(appName)@”" + } + }, + "ro" : { + "stringUnit" : { + "state" : "new", + "value" : "sign a request from “%1$(namespace)@” using secret “%2$(appName)@”" + } + }, + "ru" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "подписать запрос от “%1$(namespace)@” используя секрет “%2$(appName)@”" + } + }, + "sr" : { + "stringUnit" : { + "state" : "new", + "value" : "sign a request from “%1$(namespace)@” using secret “%2$(appName)@”" + } + }, + "sv" : { + "stringUnit" : { + "state" : "new", + "value" : "sign a request from “%1$(namespace)@” using secret “%2$(appName)@”" + } + }, + "tr" : { + "stringUnit" : { + "state" : "new", + "value" : "sign a request from “%1$(namespace)@” using secret “%2$(appName)@”" + } + }, + "uk" : { + "stringUnit" : { + "state" : "new", + "value" : "sign a request from “%1$(namespace)@” using secret “%2$(appName)@”" + } + }, + "vi" : { + "stringUnit" : { + "state" : "new", + "value" : "sign a request from “%1$(namespace)@” using secret “%2$(appName)@”" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "用密钥串“%2$(appName)@”认证来自“%1$(namespace)@”的请求" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "用Secret「%2$(appName)@」認證来自「%1$(namespace)@」的请求" + } + } + } + }, "certificate_detail_critical_options_label" : { "extractionState" : "manual", "localizations" : { @@ -23775,6 +24147,192 @@ } } }, + "signed_connection_notification_description" : { + "comment" : "When the user performs an action using a secret, they're shown a notification describing what happened. This is the description, showing which secret was used. The placeholder is the name of the secret.", + "extractionState" : "manual", + "localizations" : { + "af" : { + "stringUnit" : { + "state" : "new", + "value" : "Using secret %1$(secretName)@" + } + }, + "ar" : { + "stringUnit" : { + "state" : "new", + "value" : "Using secret %1$(secretName)@" + } + }, + "ca" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Usant secret %1$(secretName)@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "new", + "value" : "Using secret %1$(secretName)@" + } + }, + "da" : { + "stringUnit" : { + "state" : "new", + "value" : "Using secret %1$(secretName)@" + } + }, + "de" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Benutze Secret %1$(secretName)@" + } + }, + "el" : { + "stringUnit" : { + "state" : "new", + "value" : "Using secret %1$(secretName)@" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Using secret %1$(secretName)@ connecting to %2$(username)@@%3$(host)@" + } + }, + "es" : { + "stringUnit" : { + "state" : "new", + "value" : "Using secret %1$(secretName)@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Käytetään salaisuutta %1$(secretName)@" + } + }, + "fr" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Utilisation du secret %1$(secretName)@" + } + }, + "he" : { + "stringUnit" : { + "state" : "new", + "value" : "Using secret %1$(secretName)@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "new", + "value" : "Using secret %1$(secretName)@" + } + }, + "it" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Usato il Segreto %1$(secretName)@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "%1$(secretName)@を使用中" + } + }, + "ko" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "비밀 %1$(secretName)@ 사용됨" + } + }, + "nb" : { + "stringUnit" : { + "state" : "new", + "value" : "Using secret %1$(secretName)@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "new", + "value" : "Using secret %1$(secretName)@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Użyto sekretu %1$(secretName)@" + } + }, + "pt" : { + "stringUnit" : { + "state" : "new", + "value" : "Using secret %1$(secretName)@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Utilizando o segredo %1$(secretName)@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "new", + "value" : "Using secret %1$(secretName)@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Используя секрет %1$(secretName)@" + } + }, + "sr" : { + "stringUnit" : { + "state" : "new", + "value" : "Using secret %1$(secretName)@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "new", + "value" : "Using secret %1$(secretName)@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "new", + "value" : "Using secret %1$(secretName)@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "new", + "value" : "Using secret %1$(secretName)@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "new", + "value" : "Using secret %1$(secretName)@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "使用密钥串 %1$(secretName)@" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "使用Secret「%1$(secretName)@」" + } + } + } + }, "signed_notification_description" : { "comment" : "When the user performs an action using a secret, they're shown a notification describing what happened. This is the description, showing which secret was used. The placeholder is the name of the secret.", "extractionState" : "manual", @@ -24147,6 +24705,192 @@ } } }, + "signed_signature_notification_description" : { + "comment" : "When the user performs an action using a secret, they're shown a notification describing what happened. This is the description, showing which secret was used. The placeholder is the name of the secret.", + "extractionState" : "manual", + "localizations" : { + "af" : { + "stringUnit" : { + "state" : "new", + "value" : "Using secret %1$(secretName)@" + } + }, + "ar" : { + "stringUnit" : { + "state" : "new", + "value" : "Using secret %1$(secretName)@" + } + }, + "ca" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Usant secret %1$(secretName)@" + } + }, + "cs" : { + "stringUnit" : { + "state" : "new", + "value" : "Using secret %1$(secretName)@" + } + }, + "da" : { + "stringUnit" : { + "state" : "new", + "value" : "Using secret %1$(secretName)@" + } + }, + "de" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Benutze Secret %1$(secretName)@" + } + }, + "el" : { + "stringUnit" : { + "state" : "new", + "value" : "Using secret %1$(secretName)@" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Using secret %1$(secretName)@ for %2$(namespace)@" + } + }, + "es" : { + "stringUnit" : { + "state" : "new", + "value" : "Using secret %1$(secretName)@" + } + }, + "fi" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Käytetään salaisuutta %1$(secretName)@" + } + }, + "fr" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Utilisation du secret %1$(secretName)@" + } + }, + "he" : { + "stringUnit" : { + "state" : "new", + "value" : "Using secret %1$(secretName)@" + } + }, + "hu" : { + "stringUnit" : { + "state" : "new", + "value" : "Using secret %1$(secretName)@" + } + }, + "it" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Usato il Segreto %1$(secretName)@" + } + }, + "ja" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "%1$(secretName)@を使用中" + } + }, + "ko" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "비밀 %1$(secretName)@ 사용됨" + } + }, + "nb" : { + "stringUnit" : { + "state" : "new", + "value" : "Using secret %1$(secretName)@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "new", + "value" : "Using secret %1$(secretName)@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Użyto sekretu %1$(secretName)@" + } + }, + "pt" : { + "stringUnit" : { + "state" : "new", + "value" : "Using secret %1$(secretName)@" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Utilizando o segredo %1$(secretName)@" + } + }, + "ro" : { + "stringUnit" : { + "state" : "new", + "value" : "Using secret %1$(secretName)@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Используя секрет %1$(secretName)@" + } + }, + "sr" : { + "stringUnit" : { + "state" : "new", + "value" : "Using secret %1$(secretName)@" + } + }, + "sv" : { + "stringUnit" : { + "state" : "new", + "value" : "Using secret %1$(secretName)@" + } + }, + "tr" : { + "stringUnit" : { + "state" : "new", + "value" : "Using secret %1$(secretName)@" + } + }, + "uk" : { + "stringUnit" : { + "state" : "new", + "value" : "Using secret %1$(secretName)@" + } + }, + "vi" : { + "stringUnit" : { + "state" : "new", + "value" : "Using secret %1$(secretName)@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "使用密钥串 %1$(secretName)@" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "使用Secret「%1$(secretName)@」" + } + } + } + }, "smart_card" : { "extractionState" : "manual", "localizations" : { @@ -26928,5 +27672,5 @@ } } }, - "version" : "1.0" + "version" : "1.2" } \ No newline at end of file diff --git a/Sources/Packages/Sources/SecretAgentKit/Agent.swift b/Sources/Packages/Sources/SecretAgentKit/Agent.swift index 6700370..1738774 100644 --- a/Sources/Packages/Sources/SecretAgentKit/Agent.swift +++ b/Sources/Packages/Sources/SecretAgentKit/Agent.swift @@ -35,7 +35,8 @@ extension Agent { public func handle( request: SSHAgent.Request, - provenance: SigningRequestProvenance + provenance: SigningRequestProvenance, + hosts: [Data: String]? ) async -> Data { logger.debug("Agent received request of type \(request.debugDescription)") // Depending on the launch context (such as after macOS update), the agent may need to reload secrets before acting @@ -57,7 +58,8 @@ extension Agent { hasSignature: payload.hasSignature, publicKeyAlgorithm: payload.publicKeyAlgorithm, publicKey: payload.publicKey, - hostKey: payload.hostKey + hostKey: payload.hostKey, + host: hosts?[payload.hostKey] ) ) if let boundSession = await sessionID { @@ -79,7 +81,7 @@ extension Agent { } _ = target response.append(SSHAgent.Response.agentSignResponse.data) - response.append(try await sign(data: context.dataToSign.raw, keyBlob: context.keyBlob, provenance: provenance)) + response.append(try await sign(data: context.dataToSign.raw, keyBlob: context.keyBlob, provenance: provenance, target: target)) logger.debug("Agent returned \(SSHAgent.Response.agentSignResponse.debugDescription)") case .protocolExtension(.openSSH(.sessionBind(let bind))): // This is disabled until forward enforcement is handled. @@ -143,19 +145,20 @@ extension Agent { /// - data: The data to sign. /// - provenance: A ``SecretKit.SigningRequestProvenance`` object describing the origin of the request. /// - Returns: An OpenSSH formatted Data payload containing the signed data response. - func sign(data: Data, keyBlob: Data, provenance: SigningRequestProvenance) async throws -> Data { + func sign(data: Data, keyBlob: Data, provenance: SigningRequestProvenance, target: SigningRequestTarget?) async throws -> Data { guard let (secret, store) = await secret(matching: keyBlob) else { let keyBlobHex = keyBlob.formatted(.hex()) logger.debug("Agent did not have a key matching \(keyBlobHex)") throw NoMatchingKeyError() } - try await witness?.speakNowOrForeverHoldYourPeace(forAccessTo: secret, from: store, by: provenance) - let rawRepresentation = try await store.sign(data: data, with: secret, for: provenance) + try await witness?.speakNowOrForeverHoldYourPeace(forAccessTo: secret, from: store, by: provenance, target: target) + + let rawRepresentation = try await store.sign(data: data, with: secret, for: provenance, target: target) let signedData = signatureWriter.data(secret: secret, signature: rawRepresentation) - try await witness?.witness(accessTo: secret, from: store, by: provenance) + try await witness?.witness(accessTo: secret, from: store, by: provenance, target: target) logger.debug("Agent signed request") diff --git a/Sources/Packages/Sources/SecretAgentKit/SigningWitness.swift b/Sources/Packages/Sources/SecretAgentKit/SigningWitness.swift index 2e6ab49..77fc90d 100644 --- a/Sources/Packages/Sources/SecretAgentKit/SigningWitness.swift +++ b/Sources/Packages/Sources/SecretAgentKit/SigningWitness.swift @@ -1,5 +1,6 @@ import Foundation import SecretKit +import SSHProtocolKit /// A protocol that allows conformers to be notified of access to secrets, and optionally prevent access. public protocol SigningWitness: Sendable { @@ -10,13 +11,13 @@ public protocol SigningWitness: Sendable { /// - store: The `Store` being asked to sign the request.. /// - provenance: A `SigningRequestProvenance` object describing the origin of the request. /// - Note: This method being called does not imply that the requst has been authorized. If a secret requires authentication, authentication will still need to be performed by the user before the request will be performed. If the user declines or fails to authenticate, the request will fail. - func speakNowOrForeverHoldYourPeace(forAccessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance) async throws + func speakNowOrForeverHoldYourPeace(forAccessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance, target: SigningRequestTarget?) async throws /// Notifies the callee that a signing operation has been performed for a given secret. /// - Parameters: /// - secret: The `Secret` that will was used to sign the request. /// - store: The `Store` that signed the request.. /// - provenance: A `SigningRequestProvenance` object describing the origin of the request. - func witness(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance) async throws + func witness(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance, target: SigningRequestTarget?) async throws } diff --git a/Sources/Packages/Sources/SecretKit/Erasers/AnySecretStore.swift b/Sources/Packages/Sources/SecretKit/Erasers/AnySecretStore.swift index f163879..2edf2d4 100644 --- a/Sources/Packages/Sources/SecretKit/Erasers/AnySecretStore.swift +++ b/Sources/Packages/Sources/SecretKit/Erasers/AnySecretStore.swift @@ -8,7 +8,7 @@ open class AnySecretStore: SecretStore, @unchecked Sendable { private let _id: @Sendable () -> UUID private let _name: @MainActor @Sendable () -> String private let _secrets: @MainActor @Sendable () -> [AnySecret] - private let _sign: @Sendable (Data, AnySecret, SigningRequestProvenance) async throws -> Data + private let _sign: @Sendable (Data, AnySecret, SigningRequestProvenance, SigningRequestTarget?) async throws -> Data private let _existingPersistedAuthenticationContext: @Sendable (AnySecret) async -> PersistedAuthenticationContext? private let _persistAuthentication: @Sendable (AnySecret, TimeInterval) async throws -> Void private let _reloadSecrets: @Sendable () async -> Void @@ -19,7 +19,7 @@ open class AnySecretStore: SecretStore, @unchecked Sendable { _name = { secretStore.name } _id = { secretStore.id } _secrets = { secretStore.secrets.map { AnySecret($0) } } - _sign = { try await secretStore.sign(data: $0, with: $1.base as! SecretStoreType.SecretType, for: $2) } + _sign = { try await secretStore.sign(data: $0, with: $1.base as! SecretStoreType.SecretType, for: $2, target: $3) } _existingPersistedAuthenticationContext = { await secretStore.existingPersistedAuthenticationContext(secret: $0.base as! SecretStoreType.SecretType) } _persistAuthentication = { try await secretStore.persistAuthentication(secret: $0.base as! SecretStoreType.SecretType, forDuration: $1) } _reloadSecrets = { await secretStore.reloadSecrets() } @@ -41,8 +41,8 @@ open class AnySecretStore: SecretStore, @unchecked Sendable { return _secrets() } - public func sign(data: Data, with secret: AnySecret, for provenance: SigningRequestProvenance) async throws -> Data { - try await _sign(data, secret, provenance) + public func sign(data: Data, with secret: AnySecret, for provenance: SigningRequestProvenance, target: SigningRequestTarget?) async throws -> Data { + try await _sign(data, secret, provenance, target) } public func existingPersistedAuthenticationContext(secret: AnySecret) async -> PersistedAuthenticationContext? { diff --git a/Sources/Packages/Sources/SecretKit/Types/SecretStore.swift b/Sources/Packages/Sources/SecretKit/Types/SecretStore.swift index 42b4db9..a6bb0ab 100644 --- a/Sources/Packages/Sources/SecretKit/Types/SecretStore.swift +++ b/Sources/Packages/Sources/SecretKit/Types/SecretStore.swift @@ -20,7 +20,7 @@ public protocol SecretStore: Identifiable, Sendable { /// - secret: The ``Secret`` to sign with. /// - provenance: A ``SigningRequestProvenance`` describing where the request came from. /// - Returns: The signed data. - func sign(data: Data, with secret: SecretType, for provenance: SigningRequestProvenance) async throws -> Data + func sign(data: Data, with secret: SecretType, for provenance: SigningRequestProvenance, target: SigningRequestTarget?) async throws -> Data /// Checks to see if there is currently a valid persisted authentication for a given secret. /// - Parameters: diff --git a/Sources/Packages/Sources/SecretKit/Types/SigningRequestTarget.swift b/Sources/Packages/Sources/SecretKit/Types/SigningRequestTarget.swift index 351a67d..f92ac84 100644 --- a/Sources/Packages/Sources/SecretKit/Types/SigningRequestTarget.swift +++ b/Sources/Packages/Sources/SecretKit/Types/SigningRequestTarget.swift @@ -14,6 +14,7 @@ public enum SigningRequestTarget: Sendable { public let publicKeyAlgorithm: String public let publicKey: Data public let hostKey: Data + public let host: String? public init( username: String, @@ -21,12 +22,14 @@ public enum SigningRequestTarget: Sendable { publicKeyAlgorithm: String, publicKey: Data, hostKey: Data, + host: String? ) { self.username = username self.hasSignature = hasSignature self.publicKeyAlgorithm = publicKeyAlgorithm self.publicKey = publicKey self.hostKey = hostKey + self.host = host } } diff --git a/Sources/Packages/Sources/SecureEnclaveSecretKit/SecureEnclaveStore.swift b/Sources/Packages/Sources/SecureEnclaveSecretKit/SecureEnclaveStore.swift index 61fc722..497967c 100644 --- a/Sources/Packages/Sources/SecureEnclaveSecretKit/SecureEnclaveStore.swift +++ b/Sources/Packages/Sources/SecureEnclaveSecretKit/SecureEnclaveStore.swift @@ -37,13 +37,20 @@ extension SecureEnclave { // MARK: SecretStore - public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance) async throws -> Data { + public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance, target: SigningRequestTarget?) async throws -> Data { var context: LAContext if let existing = await persistentAuthenticationHandler.existingPersistedAuthenticationContext(secret: secret) { context = unsafe existing.context } else { let newContext = LAContext() - newContext.localizedReason = String(localized: .authContextRequestSignatureDescription(appName: provenance.origin.displayName, secretName: secret.name)) + switch target { + case .connection(let payload) where payload.host != nil: + newContext.localizedReason = String(localized: .authContextRequestSignatureForConnectionDescription(appName: provenance.origin.displayName, targetName: payload.host!, secretName: secret.name)) + case .signature(let payload): + newContext.localizedReason = String(localized: .authContextRequestSignatureForSignatureDescription(namespace: payload.namespace, appName: provenance.origin.displayName, secretName: secret.name)) + default: + newContext.localizedReason = String(localized: .authContextRequestSignatureDescription(appName: provenance.origin.displayName, secretName: secret.name)) + } newContext.localizedCancelTitle = String(localized: .authContextRequestDenyButton) context = newContext } diff --git a/Sources/Packages/Sources/SmartCardSecretKit/SmartCardStore.swift b/Sources/Packages/Sources/SmartCardSecretKit/SmartCardStore.swift index 77ed5e2..1efbb06 100644 --- a/Sources/Packages/Sources/SmartCardSecretKit/SmartCardStore.swift +++ b/Sources/Packages/Sources/SmartCardSecretKit/SmartCardStore.swift @@ -57,7 +57,7 @@ extension SmartCard { // MARK: Public API - public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance) async throws -> Data { + public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance, target: SigningRequestTarget?) async throws -> Data { guard let tokenID = await state.tokenID else { fatalError() } var context: LAContext if let existing = await persistentAuthenticationHandler.existingPersistedAuthenticationContext(secret: secret) { diff --git a/Sources/Packages/Tests/SecretAgentKitTests/AgentTests.swift b/Sources/Packages/Tests/SecretAgentKitTests/AgentTests.swift index 8942fb6..43ae399 100644 --- a/Sources/Packages/Tests/SecretAgentKitTests/AgentTests.swift +++ b/Sources/Packages/Tests/SecretAgentKitTests/AgentTests.swift @@ -13,7 +13,7 @@ import CertificateKit @Test func emptyStores() async throws { let agent = Agent(storeList: SecretStoreList(), certificateStore: CertificateStore()) let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestIdentities) - let response = await agent.handle(request: request, provenance: .test) + let response = await agent.handle(request: request, provenance: .test, hosts: nil) #expect(response == Constants.Responses.requestIdentitiesEmpty) } @@ -21,7 +21,7 @@ import CertificateKit let list = await storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret]) let agent = Agent(storeList: list, certificateStore: CertificateStore()) let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestIdentities) - let response = await agent.handle(request: request, provenance: .test) + let response = await agent.handle(request: request, provenance: .test, hosts: nil) let actual = OpenSSHReader(data: response) let expected = OpenSSHReader(data: Constants.Responses.requestIdentitiesMultiple) @@ -35,7 +35,7 @@ import CertificateKit let list = await storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret]) let agent = Agent(storeList: list, certificateStore: CertificateStore()) let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestSignatureWithNoneMatching) - let response = await agent.handle(request: request, provenance: .test) + let response = await agent.handle(request: request, provenance: .test, hosts: nil) #expect(response == Constants.Responses.requestFailure) } @@ -44,7 +44,7 @@ import CertificateKit guard case SSHAgent.Request.signRequest(let context) = request else { return } let list = await storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret]) let agent = Agent(storeList: list, certificateStore: CertificateStore()) - let response = await agent.handle(request: request, provenance: .test) + let response = await agent.handle(request: request, provenance: .test, hosts: nil) let responseReader = OpenSSHReader(data: response) let length = try responseReader.readNextBytes(as: UInt32.self) let type = try responseReader.readNextBytes(as: UInt8.self) @@ -79,7 +79,7 @@ import CertificateKit return true }, witness: { _, _ in }) let agent = Agent(storeList: list, certificateStore: CertificateStore(), witness: witness) - let response = await agent.handle(request: .signRequest(.empty), provenance: .test) + let response = await agent.handle(request: .signRequest(.empty), provenance: .test, hosts: nil) #expect(response == Constants.Responses.requestFailure) } @@ -93,7 +93,7 @@ import CertificateKit }) let agent = Agent(storeList: list, certificateStore: CertificateStore(), witness: witness) let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestSignature) - _ = await agent.handle(request: request, provenance: .test) + _ = await agent.handle(request: request, provenance: .test, hosts: nil) #expect(witnessed) } @@ -109,7 +109,7 @@ import CertificateKit }) let agent = Agent(storeList: list, certificateStore: CertificateStore(), witness: witness) let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestSignature) - _ = await agent.handle(request: request, provenance: .test) + _ = await agent.handle(request: request, provenance: .test, hosts: nil) #expect(witnessTrace == speakNowTrace) #expect(witnessTrace == .test) } @@ -122,7 +122,7 @@ import CertificateKit store.shouldThrow = true let agent = Agent(storeList: list, certificateStore: CertificateStore()) let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestSignature) - let response = await agent.handle(request: request, provenance: .test) + let response = await agent.handle(request: request, provenance: .test, hosts: nil) #expect(response == Constants.Responses.requestFailure) } @@ -130,7 +130,7 @@ import CertificateKit @Test func unhandledAdd() async throws { let agent = Agent(storeList: SecretStoreList(), certificateStore: CertificateStore()) - let response = await agent.handle(request: .addIdentity, provenance: .test) + let response = await agent.handle(request: .addIdentity, provenance: .test, hosts: nil) #expect(response == Constants.Responses.requestFailure) } diff --git a/Sources/Packages/Tests/SecretAgentKitTests/StubStore.swift b/Sources/Packages/Tests/SecretAgentKitTests/StubStore.swift index 381f14d..08106dd 100644 --- a/Sources/Packages/Tests/SecretAgentKitTests/StubStore.swift +++ b/Sources/Packages/Tests/SecretAgentKitTests/StubStore.swift @@ -49,7 +49,7 @@ extension Stub { print("Public Key OpenSSH: \(OpenSSHPublicKeyWriter().openSSHString(secret: secret))") } - public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance) throws -> Data { + public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance, target: SigningRequestTarget?) throws -> Data { guard !shouldThrow else { throw NSError(domain: "test", code: 0, userInfo: nil) } diff --git a/Sources/Packages/Tests/SecretAgentKitTests/StubWitness.swift b/Sources/Packages/Tests/SecretAgentKitTests/StubWitness.swift index 2fa1af8..9f31a8d 100644 --- a/Sources/Packages/Tests/SecretAgentKitTests/StubWitness.swift +++ b/Sources/Packages/Tests/SecretAgentKitTests/StubWitness.swift @@ -10,14 +10,14 @@ struct StubWitness { extension StubWitness: SigningWitness { -func speakNowOrForeverHoldYourPeace(forAccessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance) throws { + func speakNowOrForeverHoldYourPeace(forAccessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance, target: SigningRequestTarget?) throws { let objection = speakNow(secret, provenance) if objection { throw TheresMyChance() } } -func witness(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance) throws { +func witness(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance, target: SigningRequestTarget?) throws { witness(secret, provenance) } diff --git a/Sources/SecretAgent/AppDelegate.swift b/Sources/SecretAgent/AppDelegate.swift index ce8d06e..8416725 100644 --- a/Sources/SecretAgent/AppDelegate.swift +++ b/Sources/SecretAgent/AppDelegate.swift @@ -54,9 +54,11 @@ class AppDelegate: NSObject, NSApplicationDelegate { Task { do { let inputParser = try await XPCAgentInputParser() + let hostsReader = try? await XPCHostsfileReader() + let hosts = try? await hostsReader?.read() for await message in session.messages { let request = try await inputParser.parse(data: message) - let agentResponse = await agent.handle(request: request, provenance: session.provenance) + let agentResponse = await agent.handle(request: request, provenance: session.provenance, hosts: hosts) try session.write(agentResponse) } } catch { diff --git a/Sources/SecretAgent/Notifier.swift b/Sources/SecretAgent/Notifier.swift index fa48cdd..8c1268d 100644 --- a/Sources/SecretAgent/Notifier.swift +++ b/Sources/SecretAgent/Notifier.swift @@ -60,12 +60,19 @@ final class Notifier: Sendable { notificationCenter.requestAuthorization(options: .alert) { _, _ in } } - func notify(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance) async { + func notify(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance, target: SigningRequestTarget?) async { await notificationDelegate.state.setPending(secret: secret, store: store) let notificationCenter = UNUserNotificationCenter.current() let notificationContent = UNMutableNotificationContent() notificationContent.title = String(localized: .signedNotificationTitle(appName: provenance.origin.displayName)) - notificationContent.subtitle = String(localized: .signedNotificationDescription(secretName: secret.name)) + switch target { + case .connection(let payload) where payload.host != nil: + notificationContent.subtitle = String(localized: .signedConnectionNotificationDescription(secretName: secret.name, username: payload.username, host: payload.host!)) + case .signature(let payload): + notificationContent.subtitle = String(localized: .signedSignatureNotificationDescription(secretName: secret.name, namespace: payload.namespace)) + default: + notificationContent.subtitle = String(localized: .signedNotificationDescription(secretName: secret.name)) + } notificationContent.userInfo[Constants.persistSecretIDKey] = secret.id.description notificationContent.userInfo[Constants.persistStoreIDKey] = store.id.description notificationContent.interruptionLevel = .timeSensitive @@ -100,11 +107,21 @@ final class Notifier: Sendable { extension Notifier: SigningWitness { - func speakNowOrForeverHoldYourPeace(forAccessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance) async throws { + func speakNowOrForeverHoldYourPeace( + forAccessTo secret: AnySecret, + from store: AnySecretStore, + by provenance: SigningRequestProvenance, + target: SigningRequestTarget? + ) async throws { } - func witness(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance) async throws { - await notify(accessTo: secret, from: store, by: provenance) + func witness( + accessTo secret: AnySecret, + from store: AnySecretStore, + by provenance: SigningRequestProvenance, + target: SigningRequestTarget? + ) async throws { + await notify(accessTo: secret, from: store, by: provenance, target: target) } } diff --git a/Sources/SecretAgent/XPCHostsfileReader.swift b/Sources/SecretAgent/XPCHostsfileReader.swift new file mode 100644 index 0000000..9dc0754 --- /dev/null +++ b/Sources/SecretAgent/XPCHostsfileReader.swift @@ -0,0 +1,36 @@ +import Foundation +import OSLog +import XPCWrappers +import OSLog + +public final class XPCHostsfileReader { + + private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "XPCHostsfileReader") + private let session: XPCTypedSession<[Data: String], HostsfileReaderError> + + public init() async throws { + logger.debug("Creating XPCHostsfileReader") + session = try await XPCTypedSession(serviceName: "com.maxgoedjen.Secretive.SecretAgentHostsfileReader", warmup: true) + logger.debug("XPCHostsfileReader is warmed up.") + } + + public func read() async throws -> [Data: String] { + logger.debug("Reading hosts file") + defer { logger.debug("Read hosts file") } + return try await session.send(Data()) + } + + deinit { + session.complete() + } + +} + +extension XPCHostsfileReader { + + public enum HostsfileReaderError: Error, Codable { + case fileDoesNotExist + case parseError + } + +} diff --git a/Sources/SecretAgentHostsfileReader/Info.plist b/Sources/SecretAgentHostsfileReader/Info.plist new file mode 100644 index 0000000..c123a5d --- /dev/null +++ b/Sources/SecretAgentHostsfileReader/Info.plist @@ -0,0 +1,11 @@ + + + + + XPCService + + ServiceType + Application + + + diff --git a/Sources/SecretAgentHostsfileReader/SecretAgentHostsfileReader.entitlements b/Sources/SecretAgentHostsfileReader/SecretAgentHostsfileReader.entitlements new file mode 100644 index 0000000..9dca69b --- /dev/null +++ b/Sources/SecretAgentHostsfileReader/SecretAgentHostsfileReader.entitlements @@ -0,0 +1,18 @@ + + + + + com.apple.security.hardened-process + + com.apple.security.hardened-process.checked-allocations + + com.apple.security.hardened-process.dyld-ro + + com.apple.security.hardened-process.enhanced-security-version-string + 2 + com.apple.security.hardened-process.hardened-heap + + com.apple.security.hardened-process.platform-restrictions-string + 2 + + diff --git a/Sources/SecretAgentHostsfileReader/SecretAgentHostsfileReader.swift b/Sources/SecretAgentHostsfileReader/SecretAgentHostsfileReader.swift new file mode 100644 index 0000000..b9cb3ef --- /dev/null +++ b/Sources/SecretAgentHostsfileReader/SecretAgentHostsfileReader.swift @@ -0,0 +1,25 @@ +import Foundation +import OSLog +import XPCWrappers +import SSHProtocolKit + +final class SecretAgentHostsfileReader: NSObject, XPCProtocol { + + private let logger = Logger(subsystem: "com.maxgoedjen.secretive.SecretAgentHostsfileReader", category: "SecretAgentHostsfileReader") + + func process(_ data: Data) async throws -> [Data: String] { + logger.log("Parsing hostsfile") + var result: [Data: String] = [:] + for try await line in URL(filePath: NSHomeDirectory().appending("/.ssh/known_hosts")).lines { + let split = line.split(separator: " ").map(String.init) + guard split.count == 3 else { continue } + guard let decoded = Data(base64Encoded: split[2]) else { continue } + let reader = OpenSSHReader(data: decoded) + _ = try reader.readNextChunk() + let key = try reader.readNextChunk() + result[key] = split[0] + } + return result + } + +} diff --git a/Sources/SecretAgentHostsfileReader/main.swift b/Sources/SecretAgentHostsfileReader/main.swift new file mode 100644 index 0000000..4a26167 --- /dev/null +++ b/Sources/SecretAgentHostsfileReader/main.swift @@ -0,0 +1,7 @@ +import Foundation +import XPCWrappers + +let delegate = XPCServiceDelegate(exportedObject: SecretAgentHostsfileReader()) +let listener = NSXPCListener.service() +listener.delegate = delegate +listener.resume() diff --git a/Sources/Secretive.xcodeproj/project.pbxproj b/Sources/Secretive.xcodeproj/project.pbxproj index 2b6e30b..3bc58d5 100644 --- a/Sources/Secretive.xcodeproj/project.pbxproj +++ b/Sources/Secretive.xcodeproj/project.pbxproj @@ -30,6 +30,12 @@ 504788F42E681F6900B4556F /* ToolConfigurationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788F32E681F6900B4556F /* ToolConfigurationView.swift */; }; 504788F62E68206F00B4556F /* GettingStartedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788F52E68206F00B4556F /* GettingStartedView.swift */; }; 504789232E697DD300B4556F /* BoxBackgroundStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504789222E697DD300B4556F /* BoxBackgroundStyle.swift */; }; + 505402A03034B692000C3356 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5054029D3034B692000C3356 /* main.swift */; }; + 505402A13034B692000C3356 /* SecretAgentHostsfileReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5054029E3034B692000C3356 /* SecretAgentHostsfileReader.swift */; }; + 505402A33034B787000C3356 /* SecretAgentHostsfileReader.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 5054028D3034B5E3000C3356 /* SecretAgentHostsfileReader.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 505402A73034B7A4000C3356 /* XPCWrappers in Frameworks */ = {isa = PBXBuildFile; productRef = 505402A63034B7A4000C3356 /* XPCWrappers */; }; + 505402AA3034B80E000C3356 /* XPCHostsfileReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505402A93034B80E000C3356 /* XPCHostsfileReader.swift */; }; + 505402AC303594D1000C3356 /* SSHProtocolKit in Frameworks */ = {isa = PBXBuildFile; productRef = 505402AB303594D1000C3356 /* SSHProtocolKit */; }; 50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */; }; 505F5EF22FA9635700C45824 /* CertificateKit in Frameworks */ = {isa = PBXBuildFile; productRef = 505F5EF12FA9635700C45824 /* CertificateKit */; }; 50617D8323FCE48E0099B055 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617D8223FCE48E0099B055 /* App.swift */; }; @@ -110,6 +116,13 @@ remoteGlobalIDString = 501577BC2E6BC5B4004A37D0; remoteInfo = ReleasesDownloader; }; + 505402A43034B787000C3356 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 50617D7723FCE48D0099B055 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 5054028C3034B5E3000C3356; + remoteInfo = SecretAgentHostsfileReader; + }; 50692D1B2E6FDB880043C7BB /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 50617D7723FCE48D0099B055 /* Project object */; @@ -174,6 +187,7 @@ dstPath = "$(CONTENTS_FOLDER_PATH)/XPCServices"; dstSubfolderSpec = 16; files = ( + 505402A33034B787000C3356 /* SecretAgentHostsfileReader.xpc in Embed XPC Services */, 50692E6D2E6FFA5F0043C7BB /* SecretiveUpdater.xpc in Embed XPC Services */, 50E2052D2FAAB92000402380 /* SecretiveCertificateParser.xpc in Embed XPC Services */, 50692E702E6FFA6E0043C7BB /* SecretAgentInputParser.xpc in Embed XPC Services */, @@ -228,6 +242,12 @@ 504788F32E681F6900B4556F /* ToolConfigurationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolConfigurationView.swift; sourceTree = ""; }; 504788F52E68206F00B4556F /* GettingStartedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GettingStartedView.swift; sourceTree = ""; }; 504789222E697DD300B4556F /* BoxBackgroundStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoxBackgroundStyle.swift; sourceTree = ""; }; + 5054028D3034B5E3000C3356 /* SecretAgentHostsfileReader.xpc */ = {isa = PBXFileReference; explicitFileType = "wrapper.xpc-service"; includeInIndex = 0; path = SecretAgentHostsfileReader.xpc; sourceTree = BUILT_PRODUCTS_DIR; }; + 5054029C3034B692000C3356 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 5054029D3034B692000C3356 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; + 5054029E3034B692000C3356 /* SecretAgentHostsfileReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretAgentHostsfileReader.swift; sourceTree = ""; }; + 505402A83034B7D2000C3356 /* SecretAgentHostsfileReader.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SecretAgentHostsfileReader.entitlements; sourceTree = ""; }; + 505402A93034B80E000C3356 /* XPCHostsfileReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XPCHostsfileReader.swift; sourceTree = ""; }; 50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JustUpdatedChecker.swift; sourceTree = ""; }; 5059933F2E7A3B5B0092CFFA /* en */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = en; path = en.lproj/Main.storyboard; sourceTree = ""; }; 50617D7F23FCE48E0099B055 /* Secretive.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Secretive.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -290,6 +310,15 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 5054028A3034B5E3000C3356 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 505402AC303594D1000C3356 /* SSHProtocolKit in Frameworks */, + 505402A73034B7A4000C3356 /* XPCWrappers in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 50617D7C23FCE48D0099B055 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -408,6 +437,17 @@ path = Views; sourceTree = ""; }; + 5054029F3034B692000C3356 /* SecretAgentHostsfileReader */ = { + isa = PBXGroup; + children = ( + 505402A83034B7D2000C3356 /* SecretAgentHostsfileReader.entitlements */, + 5054029C3034B692000C3356 /* Info.plist */, + 5054029D3034B692000C3356 /* main.swift */, + 5054029E3034B692000C3356 /* SecretAgentHostsfileReader.swift */, + ); + path = SecretAgentHostsfileReader; + sourceTree = ""; + }; 50617D7623FCE48D0099B055 = { isa = PBXGroup; children = ( @@ -418,6 +458,7 @@ 50692D272E6FDB8D0043C7BB /* SecretiveUpdater */, 50692E662E6FF9E20043C7BB /* SecretAgentInputParser */, 50E205262FAAB82700402380 /* SecretiveCertificateParser */, + 5054029F3034B692000C3356 /* SecretAgentHostsfileReader */, 50617D8023FCE48E0099B055 /* Products */, 5099A08B240243730062B6F2 /* Frameworks */, ); @@ -431,6 +472,7 @@ 50692D122E6FDB880043C7BB /* SecretiveUpdater.xpc */, 50692E502E6FF9D20043C7BB /* SecretAgentInputParser.xpc */, 50E205142FAAB81C00402380 /* SecretiveCertificateParser.xpc */, + 5054028D3034B5E3000C3356 /* SecretAgentHostsfileReader.xpc */, ); name = Products; sourceTree = ""; @@ -529,6 +571,7 @@ 50020BAF24064869003D4025 /* AppDelegate.swift */, 5018F54E24064786002EB505 /* Notifier.swift */, 501578122E6C0479004A37D0 /* XPCInputParser.swift */, + 505402A93034B80E000C3356 /* XPCHostsfileReader.swift */, 50E2057F2FAB291E00402380 /* CertificateMigrator.swift */, 50A3B79524026B7600D209EA /* Main.storyboard */, 50A3B79824026B7600D209EA /* Info.plist */, @@ -561,6 +604,27 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 5054028C3034B5E3000C3356 /* SecretAgentHostsfileReader */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5054029B3034B5E3000C3356 /* Build configuration list for PBXNativeTarget "SecretAgentHostsfileReader" */; + buildPhases = ( + 505402893034B5E3000C3356 /* Sources */, + 5054028A3034B5E3000C3356 /* Frameworks */, + 5054028B3034B5E3000C3356 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = SecretAgentHostsfileReader; + packageProductDependencies = ( + 505402A63034B7A4000C3356 /* XPCWrappers */, + 505402AB303594D1000C3356 /* SSHProtocolKit */, + ); + productName = SecretAgentHostsfileReader; + productReference = 5054028D3034B5E3000C3356 /* SecretAgentHostsfileReader.xpc */; + productType = "com.apple.product-type.xpc-service"; + }; 50617D7E23FCE48D0099B055 /* Secretive */ = { isa = PBXNativeTarget; buildConfigurationList = 50617D9D23FCE48E0099B055 /* Build configuration list for PBXNativeTarget "Secretive" */; @@ -655,6 +719,7 @@ 50692E6F2E6FFA5F0043C7BB /* PBXTargetDependency */, 50692E722E6FFA6E0043C7BB /* PBXTargetDependency */, 50E2052F2FAAB92000402380 /* PBXTargetDependency */, + 505402A53034B787000C3356 /* PBXTargetDependency */, ); name = SecretAgent; packageProductDependencies = ( @@ -703,6 +768,9 @@ LastUpgradeCheck = 2640; ORGANIZATIONNAME = "Max Goedjen"; TargetAttributes = { + 5054028C3034B5E3000C3356 = { + CreatedOnToolsVersion = 27.0; + }; 50617D7E23FCE48D0099B055 = { CreatedOnToolsVersion = 11.3; }; @@ -746,11 +814,19 @@ 50692D112E6FDB880043C7BB /* SecretiveUpdater */, 50E205132FAAB81C00402380 /* SecretiveCertificateParser */, 50692E4F2E6FF9D20043C7BB /* SecretAgentInputParser */, + 5054028C3034B5E3000C3356 /* SecretAgentHostsfileReader */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 5054028B3034B5E3000C3356 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 50617D7D23FCE48D0099B055 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -799,6 +875,15 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 505402893034B5E3000C3356 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 505402A03034B692000C3356 /* main.swift in Sources */, + 505402A13034B692000C3356 /* SecretAgentHostsfileReader.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 50617D7B23FCE48D0099B055 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -868,6 +953,7 @@ 50020BB024064869003D4025 /* AppDelegate.swift in Sources */, 5018F54F24064786002EB505 /* Notifier.swift in Sources */, 501578132E6C0479004A37D0 /* XPCInputParser.swift in Sources */, + 505402AA3034B80E000C3356 /* XPCHostsfileReader.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -892,6 +978,11 @@ isa = PBXTargetDependency; targetProxy = 501577D32E6BC5DD004A37D0 /* PBXContainerItemProxy */; }; + 505402A53034B787000C3356 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 5054028C3034B5E3000C3356 /* SecretAgentHostsfileReader */; + targetProxy = 505402A43034B787000C3356 /* PBXContainerItemProxy */; + }; 50692D1C2E6FDB880043C7BB /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 50692D112E6FDB880043C7BB /* SecretiveUpdater */; @@ -936,6 +1027,104 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 505402973034B5E3000C3356 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_ENTITLEMENTS = SecretAgentHostsfileReader/SecretAgentHostsfileReader.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = "$(SECRETIVE_DEVELOPMENT_TEAM)"; + ENABLE_APP_SANDBOX = NO; + ENABLE_ENHANCED_SECURITY = YES; + ENABLE_HARDENED_RUNTIME = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = SecretAgentHostsfileReader/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = SecretAgentHostsfileReader; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2026 Max Goedjen. All rights reserved."; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 14.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgentHostsfileReader"; + PRODUCT_NAME = "$(TARGET_NAME)"; + REGISTER_APP_GROUPS = YES; + SKIP_INSTALL = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 505402983034B5E3000C3356 /* Test */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_ENTITLEMENTS = SecretAgentHostsfileReader/SecretAgentHostsfileReader.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + ENABLE_APP_SANDBOX = NO; + ENABLE_ENHANCED_SECURITY = YES; + ENABLE_HARDENED_RUNTIME = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = SecretAgentHostsfileReader/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = SecretAgentHostsfileReader; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2026 Max Goedjen. All rights reserved."; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 14.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgentHostsfileReader"; + PRODUCT_NAME = "$(TARGET_NAME)"; + REGISTER_APP_GROUPS = YES; + SKIP_INSTALL = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + }; + name = Test; + }; + 505402993034B5E3000C3356 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_ENTITLEMENTS = SecretAgentHostsfileReader/SecretAgentHostsfileReader.entitlements; + CODE_SIGN_IDENTITY = "Developer ID Application"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = "$(SECRETIVE_DEVELOPMENT_TEAM)"; + ENABLE_APP_SANDBOX = NO; + ENABLE_ENHANCED_SECURITY = YES; + ENABLE_HARDENED_RUNTIME = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = SecretAgentHostsfileReader/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = SecretAgentHostsfileReader; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2026 Max Goedjen. All rights reserved."; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 14.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "$(SECRETIVE_BASE_BUNDLE_ID).SecretAgentHostsfileReader"; + PRODUCT_NAME = "$(TARGET_NAME)"; + REGISTER_APP_GROUPS = YES; + SKIP_INSTALL = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; 50617D9B23FCE48E0099B055 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 508A58AB241E121B0069DC07 /* Config.xcconfig */; @@ -1658,6 +1847,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = "$(SECRETIVE_DEVELOPMENT_TEAM)"; ENABLE_APP_SANDBOX = YES; + ENABLE_ENHANCED_SECURITY = YES; ENABLE_HARDENED_RUNTIME = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; @@ -1689,6 +1879,7 @@ COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; ENABLE_APP_SANDBOX = YES; + ENABLE_ENHANCED_SECURITY = YES; ENABLE_HARDENED_RUNTIME = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; @@ -1722,6 +1913,7 @@ DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=macosx*]" = Z72PRUAWF6; ENABLE_APP_SANDBOX = YES; + ENABLE_ENHANCED_SECURITY = YES; ENABLE_HARDENED_RUNTIME = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; @@ -1747,6 +1939,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 5054029B3034B5E3000C3356 /* Build configuration list for PBXNativeTarget "SecretAgentHostsfileReader" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 505402973034B5E3000C3356 /* Debug */, + 505402983034B5E3000C3356 /* Test */, + 505402993034B5E3000C3356 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 50617D7A23FCE48D0099B055 /* Build configuration list for PBXProject "Secretive" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -1850,6 +2052,14 @@ isa = XCSwiftPackageProductDependency; productName = Brief; }; + 505402A63034B7A4000C3356 /* XPCWrappers */ = { + isa = XCSwiftPackageProductDependency; + productName = XPCWrappers; + }; + 505402AB303594D1000C3356 /* SSHProtocolKit */ = { + isa = XCSwiftPackageProductDependency; + productName = SSHProtocolKit; + }; 505F5EF12FA9635700C45824 /* CertificateKit */ = { isa = XCSwiftPackageProductDependency; productName = CertificateKit; diff --git a/Sources/Secretive/Preview Content/PreviewStore.swift b/Sources/Secretive/Preview Content/PreviewStore.swift index 63e7507..f8406bf 100644 --- a/Sources/Secretive/Preview Content/PreviewStore.swift +++ b/Sources/Secretive/Preview Content/PreviewStore.swift @@ -38,7 +38,7 @@ extension Preview { self.init(secrets: new) } - func sign(data: Data, with secret: Preview.Secret, for provenance: SigningRequestProvenance) throws -> Data { + func sign(data: Data, with secret: Preview.Secret, for provenance: SigningRequestProvenance, target: SigningRequestTarget?) throws -> Data { return data } @@ -82,7 +82,7 @@ extension Preview { self.init(secrets: new) } - func sign(data: Data, with secret: Preview.Secret, for provenance: SigningRequestProvenance) throws -> Data { + func sign(data: Data, with secret: Preview.Secret, for provenance: SigningRequestProvenance, target: SigningRequestTarget?) throws -> Data { return data } diff --git a/Sources/SecretiveCertificateParser/SecretiveCertificateParser.entitlements b/Sources/SecretiveCertificateParser/SecretiveCertificateParser.entitlements index 08818a6..e73b728 100644 --- a/Sources/SecretiveCertificateParser/SecretiveCertificateParser.entitlements +++ b/Sources/SecretiveCertificateParser/SecretiveCertificateParser.entitlements @@ -12,11 +12,11 @@ com.apple.security.hardened-process.dyld-ro - com.apple.security.hardened-process.enhanced-security-version - 1 + com.apple.security.hardened-process.enhanced-security-version-string + 2 com.apple.security.hardened-process.hardened-heap - com.apple.security.hardened-process.platform-restrictions - 2 + com.apple.security.hardened-process.platform-restrictions-string + 2