Metadata-Version: 2.4
Name: oath-external-totp
Version: 0.1.1
Summary: TOTP generation with external time sync (NTP / Google HTTP), derived from oath-toolkit
Author: Lucas Moreira
License-Expression: GPL-3.0-or-later
Project-URL: Homepage, https://www.nongnu.org/oath-toolkit/
Keywords: totp,otp,oath,ntp,google-time,2fa
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Security :: Cryptography
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: COPYING
License-File: NOTICE
Requires-Dist: pyotp>=2.9
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Dynamic: license-file

# oath-external-totp

**TOTP com sincronização de horário externo (Google / NTP) para Python**

Biblioteca Python que gera códigos de autenticação em dois fatores (TOTP) usando um relógio **confiável na internet**, em vez de depender apenas do relógio do sistema operacional — o mesmo comportamento do `oathtool --google-time` do [GNU OATH Toolkit](https://www.nongnu.org/oath-toolkit/) modificado.

---

## Índice

- [Por que este projeto existe?](#por-que-este-projeto-existe)
- [O problema: relógio errado = código errado](#o-problema-relógio-errado--código-errado)
- [A solução](#a-solução)
- [Como funciona](#como-funciona)
- [Instalação](#instalação)
- [Uso rápido](#uso-rápido)
- [Integração com projetos existentes](#integração-com-projetos-existentes)
- [Comparar relógio local vs Google](#comparar-relógio-local-vs-google)
- [Referência da API](#referência-da-api)
- [Erros comuns](#erros-comuns)
- [Testes](#testes)
- [Origem e licença](#origem-e-licença)
- [Aviso de responsabilidade](#aviso-de-responsabilidade)

---

## Por que este projeto existe?

Autenticadores como **Google Authenticator**, **Microsoft Authenticator** e apps corporativos geram códigos TOTP com base no **horário universal (UTC)** sincronizado via internet.

Muitos scripts e servidores em Python, porém, usam bibliotecas que leem apenas o relógio **local** do computador (`time.time()`). Em máquinas com:

- relógio atrasado ou adiantado;
- NTP desativado;
- VMs ou containers sem sync;
- dual boot ou BIOS com drift;

o código gerado **não bate** com o do celular — e o login falha mesmo com o segredo correto.

Extensões de navegador oferecem opções do tipo **“Sincronizar relógio com o Google”** exatamente por isso. Este pacote traz essa mesma ideia para **automação em Python** (Selenium, APIs, bots de aprovação, etc.), sem alterar o relógio do sistema.

---

## O problema: relógio errado = código errado

TOTP (RFC 6238) não é um código fixo: ele muda a cada **30 segundos** e depende de:

```
contador = floor((unix_timestamp - t0) / 30)
código   = HMAC-SHA1(segredo, contador) → 6 dígitos
```

Se o relógio do PC estiver **30 segundos ou mais** fora da janela correta, o contador cai em outro intervalo e o código fica **completamente diferente** — não é “quase igual”, é outro número.

### Exemplo real

| Fonte | Código gerado |
|--------|----------------|
| PC com relógio local desalinhado | `629185` |
| Mesmo segredo, horário Google/NTP | `236999` |
| App no celular | `236999` ✓ |

O PC estava errado; o celular e o `--google-time` concordaram.

### Sintomas no dia a dia

- Login OTP falha só em **um** servidor ou VM.
- Código “funciona às vezes” (quando o drift cai dentro da mesma janela de 30s).
- `oathtool.generate_otp()` ou `pyotp` local ≠ autenticador no telefone.
- Diferença de poucos segundos entre `date -u` e horário NTP.

---

## A solução

`oath-external-totp` busca a hora atual por:

1. **NTP** em `time.google.com` (padrão, rápido, UDP porta 123)
2. **Fallback HTTP**: `HEAD` em `www.google.com/generate_204` e leitura do header `Date`

Com esse instante UTC, calcula o TOTP — **sem mudar o relógio do sistema**.

Equivalente CLI no oathtool modificado:

```bash
oathtool --totp -b SEU_SEGREDO_BASE32 --google-time
```

Equivalente Python neste pacote:

```python
from oath_external_totp import generate_otp

generate_otp("SEU_SEGREDO_BASE32", google_time=True)
```

---

## Como funciona

```mermaid
flowchart TB
  subgraph app [Sua aplicação Python]
    call["generate_otp(secret, google_time=True)"]
  end

  subgraph sync [Sincronização externa]
    ntp["NTP → time.google.com"]
    http["HTTP Date → www.google.com"]
  end

  subgraph totp [TOTP RFC 6238]
    pyotp["pyotp · SHA1 · 30s · 6 dígitos"]
  end

  call --> ntp
  ntp -->|falha| http
  ntp -->|sucesso| pyotp
  http -->|sucesso| pyotp
  http -->|falha| err["TimeSyncError"]
  pyotp --> code["Código OTP"]
```

| Etapa | Detalhe |
|--------|---------|
| Segredo | Base32 (mesmo formato do QR code / `oathtool -b`) |
| Algoritmo | HMAC-SHA1, janela de 30s, 6 dígitos (padrão oathtool) |
| Relógio externo | NTP primeiro; HTTP só se NTP falhar (`google_time=True`) |
| Relógio local | Comportamento padrão se `google_time=False` |
| Sistema | **Não** executa `timedatectl` nem altera o relógio do SO |

---

## Instalação

### PyPI (após publicação)

```bash
pip install oath-external-totp
```

Testar antes em ambiente de staging:

```bash
pip install -i https://test.pypi.org/simple/ oath-external-totp
```

### Desenvolvimento local

```bash
git clone <seu-repositorio>
cd oath_external_totp
pip install -e ".[dev]"
```

### Em outro projeto (`requirements.txt`)

```text
oath-external-totp>=0.1.1
```

Ou path local:

```text
oath-external-totp @ file:///caminho/para/oath_external_totp
```

**Requisitos:** Python 3.9+, acesso de rede (UDP 123 e/ou HTTP 80) quando usar sync externa.

---

## Uso rápido

```python
from oath_external_totp import (
    generate_otp,
    totp_now,
    get_external_time,
    TimeSyncError,
)

secret = "IJKW6UDLNJRUQNCIK5DDGZRSG42EOVLX"  # segredo base32

# Relógio local (como pyotp / oathtool PyPI antigo)
print(generate_otp(secret))

# Horário Google/NTP — recomendado para bater com o celular
print(generate_otp(secret, google_time=True))

# Servidor NTP explícito
print(generate_otp(secret, ntp_server="time.google.com"))

# Só consultar a hora externa (UTC)
print(get_external_time(google_time=True))
```

Tratamento de erro (não gera código silenciosamente com hora errada):

```python
try:
    code = generate_otp(secret, google_time=True)
except TimeSyncError:
    print("Sem rede ou sync falhou — corrija conectividade")
```

---

## Integração com projetos existentes

### Substituir `oathtool.generate_otp` (TicketServer, Selenium, etc.)

Muitos projetos usam o pacote PyPI `oathtool`, que só lê o relógio local:

```python
import oathtool
otp_code = oathtool.generate_otp(otp_secret)
```

**Migração mínima** — mesma assinatura, com sync Google:

```python
import oath_external_totp as oathtool

otp_code = oathtool.generate_otp(otp_secret, google_time=True)
```

### Variável de ambiente (sem mudar cada chamada)

No `.env`:

```bash
OATH_GOOGLE_TIME=1
```

No código:

```python
import oath_external_totp as oathtool

# google_time=True é aplicado automaticamente
otp_code = oathtool.generate_otp(otp_secret)
```

### Automação com Selenium (fluxo típico)

```python
import oath_external_totp as oathtool

def preencher_otp(driver, otp_secret: str) -> None:
    code = oathtool.generate_otp(otp_secret, google_time=True)
    for i, ch in enumerate(code, start=1):
        driver.find_element(By.ID, f"camp{i}").send_keys(ch)
```

---

## Comparar relógio local vs Google

Útil para diagnosticar se o problema é o relógio do PC:

```bash
# oathtool C (build oath-toolkit modificado)
export LD_LIBRARY_PATH=/path/to/oath-toolkit-2.6.13/liboath/.libs
OT=/path/to/oath-toolkit-2.6.13/oathtool/.libs/oathtool
SECRET="SEU_SEGREDO_BASE32"

echo "Local (PC):    $($OT --totp -b "$SECRET")"
echo "Google (NTP):  $($OT --totp -b "$SECRET" --google-time)"
```

```python
from oath_external_totp import generate_otp

SECRET = "SEU_SEGREDO_BASE32"
print("Local (PC):   ", generate_otp(SECRET))
print("Google (NTP): ", generate_otp(SECRET, google_time=True))
```

| Resultado | Significado |
|-----------|-------------|
| Os dois **iguais** | Relógio local OK; sync externa é opcional |
| Os dois **diferentes** | PC desalinhado — use `google_time=True` |
| Igual ao **celular** | Sync correta; pode automatizar com confiança |

Ver horários em UTC:

```python
from datetime import datetime, timezone
from oath_external_totp import get_external_time

print("Local:   ", datetime.now(timezone.utc))
print("Externo:", get_external_time(google_time=True))
```

---

## Referência da API

| Função | Descrição |
|--------|-----------|
| `generate_otp(key, hotp_value=None, *, google_time=False, ntp_server=None)` | API compatível com `oathtool` PyPI + sync externa |
| `totp_now(secret, *, google_time=False, ntp_server=None)` | TOTP atual (base32) |
| `totp_at(secret, when)` | TOTP para um `datetime` UTC específico |
| `get_external_time(*, google_time=False, ntp_server=None)` | `datetime` UTC da rede |
| `fetch_ntp(host)` | Cliente NTP de baixo nível |
| `fetch_google_http_date()` | HTTP `Date` do Google |
| `TimeSyncError` | Falha de sincronização (mensagem alinhada ao oathtool C) |

### Parâmetros importantes

| Parâmetro | Efeito |
|-----------|--------|
| `google_time=True` | NTP `time.google.com` → fallback HTTP Google |
| `ntp_server="host"` | Apenas NTP no host informado |
| `hotp_value=N` | Contador TOTP fixo (avançado; ignora relógio) |
| `OATH_GOOGLE_TIME=1` | Default global para `generate_otp()` |

**Não combine** `google_time=True` com `ntp_server` na mesma chamada.

---

## Erros comuns

### `TimeSyncError: could not fetch external time; check network connection`

- Sem internet, firewall bloqueando UDP 123 ou HTTP 80
- Proxy corporativo sem saída para NTP/Google
- **Comportamento intencional:** não usa relógio local como fallback silencioso (evita OTP inválido sem aviso)

### Código diferente do celular mesmo com `google_time=True`

- Confirme que o **segredo base32** é o mesmo do autenticador
- Verifique se o serviço usa SHA1 / 30s (padrão desta lib)
- Pode estar no limite da janela de 30s — aguarde e gere de novo

### `ImportError: No module named 'oath_external_totp'`

```bash
pip install oath-external-totp
# ou no projeto:
pip install -e /caminho/oath_external_totp
```

---

## Testes

```bash
pip install -e ".[dev]"
pytest -v                 # unitários (rede mockada)
pytest -m network -v      # integração com internet real
```

Validação cruzada com oathtool C:

```bash
# Deve imprimir o mesmo código na mesma janela de 30s
OT=.../oathtool/.libs/oathtool
python -c "from oath_external_totp import generate_otp; print(generate_otp('SECRET', google_time=True))"
$OT --totp -b SECRET --google-time
```

---

## Origem e licença

Este pacote é **software livre (GPL-3.0-or-later)**.

| Componente | Origem |
|------------|--------|
| Lógica NTP + HTTP | Portada de `google_time.c` e `ntp_time.c` (modificações ao [oath-toolkit 2.6.13](https://www.nongnu.org/oath-toolkit/)) |
| TOTP | [pyotp](https://github.com/pyotp/pyotp) (MIT) |
| Projeto original | Copyright 2009-2025 Simon Josefsson |

Arquivos legais:

- [COPYING](COPYING) — texto completo da GPL-3
- [NOTICE](NOTICE) — atribuições e trabalho derivado

**Projetos proprietários:** distribuir este pacote junto com código fechado exige conformidade com a GPL (em geral, disponibilizar o código-fonte correspondente). Para produtos fechados, avalie reimplementação a partir das RFCs públicas ou consulte assessoria jurídica.

---

## Aviso de responsabilidade

Este software é fornecido **“no estado em que se se encontra” (AS IS)**, sem garantias de qualquer tipo, nos termos da **GPL-3.0**, seções 15 e 16 (isenção de garantia e limitação de responsabilidade). Veja [COPYING](COPYING).

- Não substitui autenticadores oficiais nem auditoria de segurança.
- Depende de serviços de terceiros (Google/NTP) quando `google_time=True`.
- O mantenedor **não se responsabiliza** por falhas de login, bloqueios de conta ou danos decorrentes de uso em produção.
- Em ambientes críticos, valide o comportamento no seu ambiente antes de automatizar logins.

---

## Créditos

Desenvolvido a partir de modificações ao **GNU OATH Toolkit 2.6.13** para suportar `--google-time` e `--ntp-server` no `oathtool`, portadas para Python para uso em projetos reais de automação e integração.

**Versão:** 0.1.1
