diff options
Diffstat (limited to 'crypto.go')
| -rw-r--r-- | crypto.go | 59 |
1 files changed, 51 insertions, 8 deletions
| @@ -3,13 +3,16 @@ package axon | |||
| 3 | import ( | 3 | import ( |
| 4 | "crypto/ed25519" | 4 | "crypto/ed25519" |
| 5 | "crypto/rand" | 5 | "crypto/rand" |
| 6 | "crypto/sha256" | ||
| 6 | "crypto/sha512" | 7 | "crypto/sha512" |
| 7 | "errors" | 8 | "errors" |
| 8 | "fmt" | 9 | "fmt" |
| 10 | "io" | ||
| 9 | "math/big" | 11 | "math/big" |
| 10 | 12 | ||
| 11 | "golang.org/x/crypto/chacha20poly1305" | 13 | "golang.org/x/crypto/chacha20poly1305" |
| 12 | "golang.org/x/crypto/curve25519" | 14 | "golang.org/x/crypto/curve25519" |
| 15 | "golang.org/x/crypto/hkdf" | ||
| 13 | ) | 16 | ) |
| 14 | 17 | ||
| 15 | // p is the prime for Curve25519: 2^255 - 19. | 18 | // p is the prime for Curve25519: 2^255 - 19. |
| @@ -59,7 +62,9 @@ func Ed25519PubKeyToX25519(pubKey ed25519.PublicKey) ([32]byte, error) { | |||
| 59 | 62 | ||
| 60 | den := new(big.Int).Sub(one, y) | 63 | den := new(big.Int).Sub(one, y) |
| 61 | den.Mod(den, p) | 64 | den.Mod(den, p) |
| 62 | den.ModInverse(den, p) | 65 | if den.ModInverse(den, p) == nil { |
| 66 | return [32]byte{}, errors.New("axon: degenerate public key (y=1)") | ||
| 67 | } | ||
| 63 | 68 | ||
| 64 | u := new(big.Int).Mul(num, den) | 69 | u := new(big.Int).Mul(num, den) |
| 65 | u.Mod(u, p) | 70 | u.Mod(u, p) |
| @@ -91,16 +96,43 @@ func DHSharedSecret(localPriv ed25519.PrivateKey, remotePub ed25519.PublicKey) ( | |||
| 91 | return out, nil | 96 | return out, nil |
| 92 | } | 97 | } |
| 93 | 98 | ||
| 99 | // dmKey derives a symmetric key from the raw DH shared secret using | ||
| 100 | // HKDF-SHA256. The salt is nil (HKDF uses a zero-filled hash-length salt) | ||
| 101 | // and the info string binds the key to the axon DM context. | ||
| 102 | func dmKey(shared [32]byte) ([32]byte, error) { | ||
| 103 | r := hkdf.New(sha256.New, shared[:], nil, []byte("axon-dm-v1")) | ||
| 104 | var key [32]byte | ||
| 105 | if _, err := io.ReadFull(r, key[:]); err != nil { | ||
| 106 | return [32]byte{}, fmt.Errorf("axon: hkdf: %w", err) | ||
| 107 | } | ||
| 108 | return key, nil | ||
| 109 | } | ||
| 110 | |||
| 111 | // dmAD builds the associated data for DM encryption by concatenating the | ||
| 112 | // sender and recipient Ed25519 public keys, preventing key-confusion attacks. | ||
| 113 | func dmAD(senderPub, recipientPub ed25519.PublicKey) []byte { | ||
| 114 | ad := make([]byte, 0, len(senderPub)+len(recipientPub)) | ||
| 115 | ad = append(ad, senderPub...) | ||
| 116 | ad = append(ad, recipientPub...) | ||
| 117 | return ad | ||
| 118 | } | ||
| 119 | |||
| 94 | // EncryptDM encrypts plaintext for a DM using ChaCha20-Poly1305. The | 120 | // EncryptDM encrypts plaintext for a DM using ChaCha20-Poly1305. The |
| 95 | // encryption key is the X25519 shared secret derived from senderPriv and | 121 | // encryption key is derived via HKDF-SHA256 from the X25519 shared secret |
| 96 | // recipientPub. The returned blob is: nonce (12 bytes) || ciphertext. | 122 | // of senderPriv and recipientPub. The sender and recipient public keys are |
| 123 | // bound as associated data. The returned blob is: nonce (12 bytes) || ciphertext. | ||
| 97 | func EncryptDM(senderPriv ed25519.PrivateKey, recipientPub ed25519.PublicKey, plaintext []byte) ([]byte, error) { | 124 | func EncryptDM(senderPriv ed25519.PrivateKey, recipientPub ed25519.PublicKey, plaintext []byte) ([]byte, error) { |
| 98 | shared, err := DHSharedSecret(senderPriv, recipientPub) | 125 | shared, err := DHSharedSecret(senderPriv, recipientPub) |
| 99 | if err != nil { | 126 | if err != nil { |
| 100 | return nil, fmt.Errorf("axon: dh: %w", err) | 127 | return nil, fmt.Errorf("axon: dh: %w", err) |
| 101 | } | 128 | } |
| 102 | 129 | ||
| 103 | aead, err := chacha20poly1305.New(shared[:]) | 130 | key, err := dmKey(shared) |
| 131 | if err != nil { | ||
| 132 | return nil, err | ||
| 133 | } | ||
| 134 | |||
| 135 | aead, err := chacha20poly1305.New(key[:]) | ||
| 104 | if err != nil { | 136 | if err != nil { |
| 105 | return nil, fmt.Errorf("axon: create aead: %w", err) | 137 | return nil, fmt.Errorf("axon: create aead: %w", err) |
| 106 | } | 138 | } |
| @@ -110,7 +142,10 @@ func EncryptDM(senderPriv ed25519.PrivateKey, recipientPub ed25519.PublicKey, pl | |||
| 110 | return nil, fmt.Errorf("axon: generate nonce: %w", err) | 142 | return nil, fmt.Errorf("axon: generate nonce: %w", err) |
| 111 | } | 143 | } |
| 112 | 144 | ||
| 113 | ct := aead.Seal(nil, nonce, plaintext, nil) | 145 | senderPub := senderPriv.Public().(ed25519.PublicKey) |
| 146 | ad := dmAD(senderPub, recipientPub) | ||
| 147 | |||
| 148 | ct := aead.Seal(nil, nonce, plaintext, ad) | ||
| 114 | return append(nonce, ct...), nil | 149 | return append(nonce, ct...), nil |
| 115 | } | 150 | } |
| 116 | 151 | ||
| @@ -123,7 +158,12 @@ func DecryptDM(recipientPriv ed25519.PrivateKey, senderPub ed25519.PublicKey, bl | |||
| 123 | return nil, fmt.Errorf("axon: dh: %w", err) | 158 | return nil, fmt.Errorf("axon: dh: %w", err) |
| 124 | } | 159 | } |
| 125 | 160 | ||
| 126 | aead, err := chacha20poly1305.New(shared[:]) | 161 | key, err := dmKey(shared) |
| 162 | if err != nil { | ||
| 163 | return nil, err | ||
| 164 | } | ||
| 165 | |||
| 166 | aead, err := chacha20poly1305.New(key[:]) | ||
| 127 | if err != nil { | 167 | if err != nil { |
| 128 | return nil, fmt.Errorf("axon: create aead: %w", err) | 168 | return nil, fmt.Errorf("axon: create aead: %w", err) |
| 129 | } | 169 | } |
| @@ -136,9 +176,12 @@ func DecryptDM(recipientPriv ed25519.PrivateKey, senderPub ed25519.PublicKey, bl | |||
| 136 | nonce := blob[:nonceSize] | 176 | nonce := blob[:nonceSize] |
| 137 | ct := blob[nonceSize:] | 177 | ct := blob[nonceSize:] |
| 138 | 178 | ||
| 139 | pt, err := aead.Open(nil, nonce, ct, nil) | 179 | recipientPub := recipientPriv.Public().(ed25519.PublicKey) |
| 180 | ad := dmAD(senderPub, recipientPub) | ||
| 181 | |||
| 182 | pt, err := aead.Open(nil, nonce, ct, ad) | ||
| 140 | if err != nil { | 183 | if err != nil { |
| 141 | return nil, fmt.Errorf("axon: decrypt: authentication failed") | 184 | return nil, fmt.Errorf("axon: decrypt: %w", err) |
| 142 | } | 185 | } |
| 143 | return pt, nil | 186 | return pt, nil |
| 144 | } | 187 | } |
