Skip to content

Commit a6092e5

Browse files
therealpaulggclaudePaul Gellai
authored
Migrate from ECDSA to post-quantum cryptography (ML-DSA-65 + ML-KEM-768) (#77)
* feat: replace classical cryptography with post-quantum algorithms (ML-KEM-768 + ML-DSA-65) Replace all quantum-vulnerable cryptographic operations with NIST-standardized post-quantum algorithms to protect against future quantum computer attacks: - Key generation: ECDSA P-256 → ML-DSA-65 (FIPS 204) for signing + ML-KEM-768 (FIPS 203) for key encapsulation - JWT signing: ES512 (ECDSA+SHA-512) → ML-DSA-65 with custom JWT implementation - Key exchange/encryption: ECDH-ES+A256KW (JWE) → ML-KEM-768 encapsulation + AES-256-GCM - AES-256-GCM symmetric encryption: unchanged (already quantum-resistant) Dependencies: - Add github.com/cloudflare/circl for ML-DSA-65 digital signatures - Use Go 1.24 stdlib crypto/mlkem for ML-KEM-768 key encapsulation - Remove github.com/lestrrat-go/jwx/v2 (no longer needed) - Bump minimum Go version to 1.24.0 Key format changes: - keypair/keypair.pub now use PEM-encoded ML-DSA-65 + ML-KEM-768 keys - master_key uses ML-KEM ciphertext + AES-GCM encrypted format - JWT tokens use "MLDSA65" algorithm identifier Closes #57 https://claude.ai/code/session_017C6wHEG2Czc1NrNWWb4aCL * feat: add backward-compatible migration path for legacy EC keys Restore lestrrat-go/jwx dependency and implement dual-path crypto that auto-detects whether the user has legacy ECDSA/ECDH-ES keys or new post-quantum ML-DSA-65/ML-KEM-768 keys: - Add keyformat.go with PEM block type detection (EC PRIVATE KEY vs MLDSA65 PRIVATE KEY) to determine format at runtime - All crypto operations (encrypt, decrypt, JWT signing, master key retrieval) now branch based on detected key format - Legacy users continue using ES512 JWTs + ECDH-ES JWE unchanged - New setups generate post-quantum keys and use ML-DSA-65 + ML-KEM-768 - EncryptWithPublicKey auto-detects server key format (EC PEM vs ML-KEM PEM) for cross-format challenge-response compatibility - RetrieveMasterKey handles both JWE and ML-KEM encrypted master keys This ensures existing users are not broken while new users get quantum-resistant cryptography by default. Closes #57 https://claude.ai/code/session_017C6wHEG2Czc1NrNWWb4aCL * feat: add `migrate` command for upgrading keys to post-quantum crypto Add a new CLI command `ssh-sync migrate` that upgrades existing users from classical ECDSA/ECDH-ES keys to post-quantum ML-DSA-65/ML-KEM-768. Migration flow: 1. Decrypt master key using the old EC keypair 2. Obtain a JWT signed with the old EC key (for server auth) 3. Back up all key files (keypair, keypair.pub, master_key) 4. Generate new ML-DSA-65 + ML-KEM-768 keypairs 5. Re-encrypt master key with new ML-KEM-768 encapsulation key 6. Upload new public key to server via PUT /api/v1/machines/key 7. Clean up backups on success Safety features: - Detects if keys are already PQ and skips migration - Creates .bak files before modifying anything - Full rollback on any failure (restores backups) - Pre-obtains auth token before key rotation (server still has old public key at upload time) - User confirmation prompt before proceeding Works with the server-side PUT /api/v1/machines/key endpoint from ssh-sync-server PR #37. Closes #57 https://claude.ai/code/session_017C6wHEG2Czc1NrNWWb4aCL * feat: unify PQ keypairs into single master seed with HKDF derivation Instead of storing two separate PQ keypairs (ML-DSA-65 + ML-KEM-768), derive both deterministically from a single 64-byte master seed using HKDF-SHA256 with domain separation. This restores the original single- keypair mental model. Key changes: - Private key file: single PEM block "SSHSYNC PQ MASTER SEED" (64 bytes) - keypair.pub (server upload): ML-DSA-65 only (server needs only this) - WebSocket PublicKeyDto: both ML-DSA + ML-KEM (for client-to-client) - Backward compat: supports 3 private key formats (EC, separate PQ, seed) https://claude.ai/code/session_017C6wHEG2Czc1NrNWWb4aCL * feat: add --classic flag to setup for optional EC key generation Users can now opt into classical ECDSA P-256 / ECDH-ES cryptography during setup with `ssh-sync setup --classic`. Post-quantum (ML-DSA-65 + ML-KEM-768) remains the default. The existing auto-detection in encrypt/decrypt/tokengen handles both formats transparently at runtime. https://claude.ai/code/session_017C6wHEG2Czc1NrNWWb4aCL * fix: match generateKeyClassic signature to original generateKey Restore the original (*ecdsa.PrivateKey, *ecdsa.PublicKey, error) return signature and keep the function body identical to the main branch version. https://claude.ai/code/session_017C6wHEG2Czc1NrNWWb4aCL * refactor: remove dead legacy PQ PEM format support The intermediate format (separate MLDSA65/MLKEM768 PEM blocks) was never released. Only two formats exist: EC (classic) and SSHSYNC PQ MASTER SEED. Remove fallback paths for the unreleased format. https://claude.ai/code/session_017C6wHEG2Czc1NrNWWb4aCL * refactor: separate signing and encapsulation keys in PublicKeyDto PublicKey should only carry the signing/identity key (EC or ML-DSA). Add a dedicated EncapsulationKey field for the ML-KEM encapsulation key so the server can store and relay them independently. - Add EncapsulationKey field to PublicKeyDto and ChallengeSuccessEncryptedKeyDto - Replace BuildFullPublicKeyPEM with BuildPQPublicKeys (returns separate PEMs) - Replace EncryptWithPublicKey with EncryptWithPQPublicKey and EncryptWithECPublicKey - Remove unused DetectPEMKeyFormat / DetectPublicKeyFormat https://claude.ai/code/session_017C6wHEG2Czc1NrNWWb4aCL * start using shared library for my sanity * remove go work * refactor: replace ML-DSA + ML-KEM with hybrid ECDH P-256 + ML-KEM-768 ML-DSA has no Go stdlib implementation yet (expected Go 1.27). Drop circl dependency and replace the pure PQ scheme with a hybrid KEM that combines ECDH P-256 and ML-KEM-768. This protects the master key against "store now, decrypt later" quantum attacks while keeping production-ready ECDSA for authentication. New default format (FormatHybrid): - Auth: ECDSA P-256 JWT (ES256), derived from hybrid seed - Key encapsulation: ephemeral ECDH + ML-KEM-768 → HKDF → AES-256-GCM - Ciphertext: [65B eph EC pub][1088B ML-KEM ct][12B nonce][AES-GCM ct] - Seed storage: single "SSHSYNC HYBRID SEED" PEM block (64 bytes) - Public key file: "PUBLIC KEY" (PKIX EC) + "MLKEM768 ENCAPSULATION KEY" --classic flag: unchanged pure-EC mode (ECDH-ES+A256KW, ECDSA JWT) FormatLegacyEC path: unchanged for existing users Key changes: - pqseed.go → hybridseed.go (DeriveHybridKeys: EC + ML-KEM from HKDF) - FormatPostQuantum → FormatHybrid - EncryptHybrid/DecryptHybrid: ECDH + ML-KEM combined via HKDF - getTokenPQ (ML-DSA) → getTokenHybrid (ECDSA ES256) - Remove all ML-DSA code and circl dependency https://claude.ai/code/session_017C6wHEG2Czc1NrNWWb4aCL * fix: convert *ecdsa.PublicKey to *ecdh.PublicKey in EncryptWithHybridPublicKey x509.ParsePKIXPublicKey returns *ecdsa.PublicKey for P-256 keys, not *ecdh.PublicKey. Use ecdsa.PublicKey.ECDH() to convert before passing to the hybrid KEM encrypt function. https://claude.ai/code/session_017C6wHEG2Czc1NrNWWb4aCL * refactor: replace hybrid ECDH+ML-KEM with full ML-DSA-65 + ML-KEM-768 PQ scheme Drop the hybrid ECDH P-256 + ML-KEM-768 approach in favor of a full post-quantum scheme using ML-DSA-65 (filippo.io/mldsa) for digital signatures and ML-KEM-768 (crypto/mlkem stdlib) for key encapsulation. Key changes: - Rename hybridseed.go → pqseed.go: DerivePQKeys returns ML-DSA-65 private key + ML-KEM-768 decapsulation key from master seed - keyformat.go: FormatHybrid → FormatPostQuantum, PEM type "SSHSYNC PQ MASTER SEED" - encrypt.go/decrypt.go: ML-KEM-768 only (no ECDH), simplified ciphertext format [1088B ML-KEM ct][12B nonce][AES-GCM ct+tag] - tokengen.go: JWT signed with ML-DSA-65 (custom "MLDSA65" alg header) instead of ECDSA ES256 - keyretrieval.go: RetrieveSigningKey (ML-DSA-65), BuildPQPublicKeys returns ML-DSA-65 pub + ML-KEM encapsulation key - setup.go: generateKey writes ML-DSA-65 + ML-KEM-768 public keys - challenge-response.go: uses EncryptWithPQPublicKey (ML-KEM only) - go.mod: add filippo.io/mldsa, remove ECDH dependencies Note: go.sum not updated (requires network access). Run `go mod tidy` to resolve filippo.io/mldsa version. https://claude.ai/code/session_017C6wHEG2Czc1NrNWWb4aCL * get package actually working * fix: align with filippo.io/mldsa actual API The mldsa package uses a single PrivateKey/PublicKey type with parameter sets (MLDSA65()) rather than suffixed types (PrivateKey65). - NewPrivateKey(mldsa.MLDSA65(), seed) instead of NewPrivateKey65(seed) - mldsa.PrivateKeySize instead of mldsa.SeedSize65 - mldsa.Verify(pk, msg, sig, nil) instead of mldsa.Verify65(pk, msg, sig) - *mldsa.PrivateKey / *mldsa.PublicKey instead of *mldsa.PrivateKey65 etc. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * no more hybrid crypto scheme, do not save MLKEM768 * simplify master seed * pr comments * test updates * Remove keypair.pub file for post-quantum keys Since ML-DSA-65 and ML-KEM-768 public keys are deterministically derived from the master seed via HKDF, there is no need to persist them in a keypair.pub file. generateKey() now only writes the seed to keypair. Public keys are built on demand via BuildPQPublicKeys() when needed for server upload (new account setup and migration). Also remove keypair.pub backup/restore from the migration flow. https://claude.ai/code/session_017C6wHEG2Czc1NrNWWb4aCL * Fix: only send ML-DSA signing key to server, not encapsulation key The server only needs the ML-DSA public key for JWT verification. The ML-KEM encapsulation key is only relevant in the existing-account setup websocket flow where Machine A needs it to encrypt the master key. newAccountSetup() and uploadMigratedKey() were incorrectly concatenating both PEM blocks into the multipart upload. https://claude.ai/code/session_017C6wHEG2Czc1NrNWWb4aCL * Split BuildPQPublicKeys into separate MLDSA and MLKEM functions BuildMLDSAPublicKeyPEM() returns just the signing public key PEM. BuildMLKEMEncapsulationKeyPEM() returns just the encapsulation key PEM. https://claude.ai/code/session_017C6wHEG2Czc1NrNWWb4aCL * remove obvious comments * remove "legacy" language * remove comments... * leave acceptable "step" comments * improve comments * simplify signature of generateKeyClassic * simplify code, add new util * remove unnecessary mocks * simplify aes encryption, code deduplication * add salt * add comment clarifying salt length * remove HKDF for ML-KEM crypto functions (unnecessary) * move files around * remove hkdf, use seed * tidy * deslopt * error handling, write on log * replace const * delete bad verify method and tests * token testing - use draft ietf ML-DSA spec * idk why this got changed * consistency i guess * nitpicky, rename to ML-DSA --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Paul Gellai <Paul.Gellai+cvna@carvana.com>
1 parent 012687a commit a6092e5

30 files changed

+1188
-329
lines changed

go.mod

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
module github.com/therealpaulgg/ssh-sync
22

3-
go 1.23.0
4-
5-
toolchain go1.24.1
3+
go 1.26.0
64

75
require (
6+
filippo.io/mldsa v0.0.0-20260215214346-43d0283efc3e
87
github.com/charmbracelet/bubbles v0.20.0
98
github.com/charmbracelet/bubbletea v1.1.0
109
github.com/charmbracelet/lipgloss v0.13.1
11-
github.com/gobwas/ws v1.1.0
12-
github.com/google/uuid v1.3.0
10+
github.com/gobwas/ws v1.4.0
11+
github.com/google/uuid v1.6.0
1312
github.com/lestrrat-go/jwx/v2 v2.0.21
1413
github.com/samber/lo v1.37.0
1514
github.com/stretchr/testify v1.9.0
15+
github.com/therealpaulgg/ssh-sync-common v0.0.1
1616
github.com/urfave/cli/v2 v2.23.7
1717
)
1818

@@ -47,11 +47,11 @@ require (
4747
github.com/sahilm/fuzzy v0.1.1 // indirect
4848
github.com/segmentio/asm v1.2.0 // indirect
4949
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
50-
golang.org/x/crypto v0.35.0 // indirect
50+
golang.org/x/crypto v0.48.0 // indirect
5151
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect
52-
golang.org/x/sync v0.11.0 // indirect
53-
golang.org/x/sys v0.30.0 // indirect
54-
golang.org/x/text v0.22.0 // indirect
52+
golang.org/x/sync v0.19.0 // indirect
53+
golang.org/x/sys v0.41.0 // indirect
54+
golang.org/x/text v0.34.0 // indirect
5555
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
5656
gopkg.in/yaml.v3 v3.0.1 // indirect
5757
)

go.sum

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
filippo.io/mldsa v0.0.0-20260215214346-43d0283efc3e h1:VsUbObBMxXlc23Eb9VeeJYE4jvTs87qa5RqSN2U5FJU=
2+
filippo.io/mldsa v0.0.0-20260215214346-43d0283efc3e/go.mod h1:32qQ5yj3R24Eu03iWFWchdC3OB653wPvoepWejkefbY=
13
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
24
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
35
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
@@ -25,12 +27,12 @@ github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU
2527
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
2628
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
2729
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
28-
github.com/gobwas/ws v1.1.0 h1:7RFti/xnNkMJnrK7D1yQ/iCIB5OrrY/54/H930kIbHA=
29-
github.com/gobwas/ws v1.1.0/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0=
30+
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
31+
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
3032
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
3133
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
32-
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
33-
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
34+
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
35+
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
3436
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
3537
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
3638
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -82,23 +84,24 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
8284
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
8385
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
8486
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
87+
github.com/therealpaulgg/ssh-sync-common v0.0.1 h1:jGF8W/mS7YE0Le8jny+qfNYUcTayN1pfsp71QXFC9Ys=
88+
github.com/therealpaulgg/ssh-sync-common v0.0.1/go.mod h1:eGg17M5ihJpAIJ7RDXot0UDK6K4wRTHciY1rPJolaDU=
8589
github.com/urfave/cli/v2 v2.23.7 h1:YHDQ46s3VghFHFf1DdF+Sh7H4RqhcM+t0TmZRJx4oJY=
8690
github.com/urfave/cli/v2 v2.23.7/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
8791
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
8892
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
89-
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
90-
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
93+
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
94+
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
9195
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM=
9296
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
93-
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
94-
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
95-
golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
97+
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
98+
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
9699
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
97100
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
98-
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
99-
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
100-
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
101-
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
101+
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
102+
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
103+
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
104+
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
102105
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
103106
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
104107
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

main.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,14 @@ func main() {
2121
{
2222
Name: "setup",
2323
Description: "Set up your system to use ssh-sync.",
24-
Action: actions.Setup,
24+
Flags: []cli.Flag{
25+
&cli.BoolFlag{
26+
Name: "classic",
27+
Aliases: []string{"c"},
28+
Usage: "Use classical elliptic curve cryptography (ECDSA/ECDH-ES) instead of post-quantum",
29+
},
30+
},
31+
Action: actions.Setup,
2532
},
2633
{
2734
Name: "upload",
@@ -63,6 +70,11 @@ func main() {
6370
Name: "reset",
6471
Action: actions.Reset,
6572
},
73+
{
74+
Name: "migrate",
75+
Description: "Migrate keys from classical ECDSA to post-quantum (ML-DSA-65 + ML-KEM-768)",
76+
Action: actions.Migrate,
77+
},
6678
{
6779
Name: "interactive",
6880
Description: "Uses a TUI mode for interacting with keys and config",

pkg/actions/challenge-response.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import (
88
"os"
99

1010
"github.com/gobwas/ws"
11-
"github.com/therealpaulgg/ssh-sync/pkg/dto"
11+
"github.com/therealpaulgg/ssh-sync-common/pkg/dto"
12+
"github.com/therealpaulgg/ssh-sync-common/pkg/wsutils"
1213
"github.com/therealpaulgg/ssh-sync/pkg/utils"
1314
"github.com/urfave/cli/v2"
1415
)
@@ -54,24 +55,29 @@ func ChallengeResponse(c *cli.Context) error {
5455
return err
5556
}
5657
}
57-
if err := utils.WriteClientMessage(&conn, dto.ChallengeResponseDto{
58+
if err := wsutils.WriteClientMessage(&conn, dto.ChallengeResponseDto{
5859
Challenge: answer,
5960
}); err != nil {
6061
return err
6162
}
62-
response, err := utils.ReadServerMessage[dto.ChallengeSuccessEncryptedKeyDto](&conn)
63+
response, err := wsutils.ReadServerMessage[dto.ChallengeSuccessEncryptedKeyDto](&conn)
6364
if err != nil {
6465
return err
6566
}
6667
masterKey, err := utils.RetrieveMasterKey()
6768
if err != nil {
6869
return err
6970
}
70-
encryptedMasterKey, err := utils.EncryptWithPublicKey(masterKey, response.Data.PublicKey)
71+
var encryptedMasterKey []byte
72+
if len(response.Data.EncapsulationKey) > 0 {
73+
encryptedMasterKey, err = utils.EncryptWithPQPublicKey(masterKey, response.Data.EncapsulationKey)
74+
} else {
75+
encryptedMasterKey, err = utils.EncryptWithECPublicKey(masterKey, response.Data.PublicKey)
76+
}
7177
if err != nil {
7278
return err
7379
}
74-
if err := utils.WriteClientMessage(&conn, dto.EncryptedMasterKeyDto{EncryptedMasterKey: encryptedMasterKey}); err != nil {
80+
if err := wsutils.WriteClientMessage(&conn, dto.EncryptedMasterKeyDto{EncryptedMasterKey: encryptedMasterKey}); err != nil {
7581
return err
7682
}
7783
fmt.Println("Challenge has been successfully completed and your new encrypted master key has been sent to server. You may now use ssh-sync on your new machine.")

pkg/actions/download.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"path/filepath"
88

99
"github.com/samber/lo"
10-
"github.com/therealpaulgg/ssh-sync/pkg/dto"
10+
"github.com/therealpaulgg/ssh-sync-common/pkg/dto"
1111
"github.com/therealpaulgg/ssh-sync/pkg/models"
1212
"github.com/therealpaulgg/ssh-sync/pkg/retrieval"
1313
"github.com/therealpaulgg/ssh-sync/pkg/utils"

pkg/actions/interactive/states/delete-ssh-key.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import (
44
"fmt"
55

66
tea "github.com/charmbracelet/bubbletea"
7-
"github.com/therealpaulgg/ssh-sync/pkg/dto"
7+
"github.com/therealpaulgg/ssh-sync-common/pkg/dto"
88
"github.com/therealpaulgg/ssh-sync/pkg/retrieval"
99
"github.com/therealpaulgg/ssh-sync/pkg/utils"
1010
)

pkg/actions/interactive/states/ssh-key-content.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99
"github.com/charmbracelet/bubbles/viewport"
1010
tea "github.com/charmbracelet/bubbletea"
1111
"github.com/charmbracelet/lipgloss"
12-
"github.com/therealpaulgg/ssh-sync/pkg/dto"
12+
"github.com/therealpaulgg/ssh-sync-common/pkg/dto"
1313
)
1414

1515
// SSHKeyContent

pkg/actions/interactive/states/ssh-key-manager.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import (
55

66
"github.com/charmbracelet/bubbles/list"
77
tea "github.com/charmbracelet/bubbletea"
8-
"github.com/therealpaulgg/ssh-sync/pkg/dto"
8+
"github.com/therealpaulgg/ssh-sync-common/pkg/dto"
99
"github.com/therealpaulgg/ssh-sync/pkg/retrieval"
1010
"github.com/therealpaulgg/ssh-sync/pkg/utils"
1111
)

pkg/actions/interactive/states/ssh-key-options.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package states
33
import (
44
"github.com/charmbracelet/bubbles/list"
55
tea "github.com/charmbracelet/bubbletea"
6-
"github.com/therealpaulgg/ssh-sync/pkg/dto"
6+
"github.com/therealpaulgg/ssh-sync-common/pkg/dto"
77
)
88

99
// SSHKeyOptions

0 commit comments

Comments
 (0)