├── LICENSE ├── README ├── cpace.go ├── cpace_test.go ├── go.mod └── go.sum /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Google LLC 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google LLC nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING 2 | W G 3 | W This is an experimental implementation. It might change and be broken G 4 | W in unexpected ways. I know you read this on a lot of docs, but this G 5 | W so far is really only a weekend project. It's also not standardized. G 6 | W G 7 | WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING 8 | 9 | filippo.io/cpace 10 | ---------------- 11 | 12 | filippo.io/cpace is a Go implementation of the CPace PAKE, 13 | instantiated with the ristretto255 group. 14 | 15 | Usage 16 | ===== 17 | 18 | https://pkg.go.dev/filippo.io/cpace 19 | 20 | Details 21 | ======= 22 | 23 | This implementation is loosely based on draft-haase-cpace-01, with ristretto255 24 | swapped in, and a sprinkle of HKDF. 25 | 26 | Using a properly abstracted prime order group such as ristretto255 allows us to 27 | ignore most of the complexity of the spec, and is an excellent case study for 28 | the value of a tight group abstraction. 29 | 30 | 1. Since the group has prime order, we don't need cofactor clearing and we don't 31 | need any low order point checks. 32 | 33 | 2. The group natively provides encoding, decoding, and map to group. 34 | 35 | 3. Since the decoding function only works for valid encodings of valid elements, 36 | we don't need any wrong curve checks. 37 | 38 | 4. Equivalent elliptic curve points are abstracted away by encoding and 39 | decoding, so we don't need to worry about the quadratic twist at all. 40 | 41 | 5. There is no x-coordinate arithmetic to account for, so there's no need to 42 | operate on a group modulo negation or to ever clear the sign. 43 | 44 | 6. We can probably even skip the identity element check, but we do it anyway. 45 | 46 | The salt/sid is under-specified, and it's unclear what properties it needs to 47 | have (random? unpredictable? not controlled by a MitM?). This implementation 48 | always has the initiator generate it randomly and send it to the peer, as 49 | allowed by the I-D. This seems safer than letting the user decide; if the higher 50 | level protocol has a session ID, it should be included in the CI as additional 51 | data, ideally along with a full transcript of the protocol that led to the 52 | selection of this PAKE. 53 | 54 | Simply concatenating variable-length, possibly attacker controlled values as the 55 | I-D suggests is dangerous. For example, the (idA, idB) pairs ("ax", "b") and 56 | ("a", "xb") would result equivalent. Instead, this implementation uses HKDF to 57 | separate secret material, salt, and context, and a uint16-length prefixed 58 | serialization for CI. 59 | 60 | The API allows two possible misuses that maybe should be blocked, depending on 61 | how severely they would break security guarantees: identities can be empty; and 62 | A's state can be used multiple times with different B messages. 63 | 64 | There should probably be a higher level API that also incorporates an HMAC 65 | verifier, checking that the peer does indeed have the password and agree on the 66 | context. Such API should withold the key from B until getting A's HMAC back one 67 | RTT later, or only expose it through a loud documented method for 0.5-RTT data. 68 | 69 | It would be interesting to provide a symmetric API, but it's unclear how the 70 | salt would be selected (and see above about my uncertainty on its properties). 71 | 72 | Flow diagram 73 | ============ 74 | 75 | A, B: ci = "cpace-r255" || idA || idB || ad 76 | A: salt = random_bytes() 77 | A: a = random_scalar() 78 | A: A = a * hashToGroup(HKDF(pw, salt, ci)) 79 | A->B: salt, A 80 | B: b = random_scalar() 81 | B: B = b * hashToGroup(HKDF(pw, salt, ci)) 82 | B: K_b = HMAC(salt || A || B, b * A) 83 | A<-B: B 84 | A: K_a = HMAC(salt || A || B, a * B) 85 | -------------------------------------------------------------------------------- /cpace.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google LLC 2 | // 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | // Package cpace implements the CPace password authenticated key exchange (PAKE) 8 | // instantiated with the ristretto255 group. 9 | // 10 | // PAKEs allow two peers to establish a shared secret key if they agree on a 11 | // password or similar low-entropy value, without letting eavesdropping or 12 | // machine-in-the-middle attackers make multiple attempts at guessing the 13 | // password value. CPace is a balanced PAKE, meaning that both peers need to 14 | // know the password plaintext. 15 | // 16 | // This implementation is loosely based on draft-haase-cpace-01. 17 | package cpace 18 | 19 | import ( 20 | "crypto/hmac" 21 | "crypto/rand" 22 | "crypto/sha256" 23 | "errors" 24 | 25 | "github.com/gtank/ristretto255" 26 | "golang.org/x/crypto/cryptobyte" 27 | "golang.org/x/crypto/hkdf" 28 | ) 29 | 30 | // ContextInfo captures the additional connection information that the two peers 31 | // need to agree on for the key to be the same. 32 | type ContextInfo struct { 33 | idA, idB string 34 | ad []byte 35 | } 36 | 37 | // NewContextInfo returns a ContextInfo for use with Start or Exchange. 38 | // 39 | // idA represents the identity of the party that uses Start, idB of the party 40 | // that uses Exchange. Identities could be MAC addresses, or IPs and ports. 41 | // 42 | // ad is any additional context the two parties share, and can be nil. Examples 43 | // of values that could be included in ad to protect against protocol downgrade 44 | // and mismatch attacks are the name and transcript of the higher level 45 | // protocol, including any negotiation inputs that led to the use of this PAKE. 46 | func NewContextInfo(idA, idB string, ad []byte) *ContextInfo { 47 | return &ContextInfo{ 48 | idA: idA, idB: idB, ad: ad, 49 | } 50 | } 51 | 52 | func (c *ContextInfo) validate() error { 53 | switch { 54 | case c == nil: 55 | return errors.New("cpace: ContextInfo can't be nil") 56 | case len(c.idA) >= 1<<16: 57 | return errors.New("cpace: idA too long") 58 | case len(c.idB) >= 1<<16: 59 | return errors.New("cpace: idB too long") 60 | case len(c.ad) >= 1<<16: 61 | return errors.New("cpace: additional data too long") 62 | default: 63 | return nil 64 | } 65 | } 66 | 67 | const label = "cpace-r255" 68 | 69 | func (c *ContextInfo) serialize() []byte { 70 | b := &cryptobyte.Builder{} 71 | for _, in := range [][]byte{ 72 | []byte(label), []byte(c.idA), []byte(c.idB), c.ad, 73 | } { 74 | b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { 75 | b.AddBytes(in) 76 | }) 77 | } 78 | return b.BytesOrPanic() 79 | } 80 | 81 | func secretGenerator(password string, salt []byte, c *ContextInfo) *ristretto255.Element { 82 | h := hkdf.New(sha256.New, []byte(password), salt, c.serialize()) 83 | b := make([]byte, 64) 84 | h.Read(b) 85 | return ristretto255.NewElement().FromUniformBytes(b) 86 | } 87 | 88 | func randomScalar() (*ristretto255.Scalar, error) { 89 | b := make([]byte, 64) 90 | if _, err := rand.Read(b); err != nil { 91 | return nil, err 92 | } 93 | return ristretto255.NewScalar().FromUniformBytes(b), nil 94 | } 95 | 96 | // State is a PAKE session in progress, where the initiating party is waiting 97 | // for the peer response. 98 | type State struct { 99 | transcript []byte 100 | secret *ristretto255.Scalar 101 | } 102 | 103 | // Start initiates a new PAKE exchange authenticated by password. msgA should be 104 | // sent to the peer, to be processed by Exchange, and s used to process the 105 | // peer's response. 106 | func Start(password string, c *ContextInfo) (msgA []byte, s *State, err error) { 107 | if err := c.validate(); err != nil { 108 | return nil, nil, err 109 | } 110 | 111 | s = &State{} 112 | 113 | salt := make([]byte, 16, 16+32) 114 | if _, err := rand.Read(salt); err != nil { 115 | return nil, nil, err 116 | } 117 | 118 | s.secret, err = randomScalar() 119 | if err != nil { 120 | return nil, nil, err 121 | } 122 | 123 | A := secretGenerator(password, salt, c) 124 | A.ScalarMult(s.secret, A) 125 | 126 | msgA = A.Encode(salt) 127 | 128 | s.transcript = make([]byte, 16+32, 16+32+32) 129 | copy(s.transcript, msgA) 130 | 131 | return msgA, s, nil 132 | } 133 | 134 | var identity = ristretto255.NewElement() 135 | 136 | func deriveKey(peerElement, transcript []byte, secret *ristretto255.Scalar) ([]byte, error) { 137 | K := ristretto255.NewElement() 138 | if err := K.Decode(peerElement); err != nil { 139 | return nil, errors.New("cpace: invalid peer message") 140 | } 141 | K.ScalarMult(secret, K) 142 | 143 | // draft-haase-cpace-01 requires checking for the identity element at this 144 | // stage, but also overloads the identity as the output for invalid peer 145 | // points (for curves where that check is necessary like P-256) and for 146 | // degenerate scalar multiplications (for curves with cofactors). In a safe 147 | // prime order group with an error-checking decode function such as 148 | // ristretto255, this should not be necessary, as the transcript hash 149 | // guarantees contributory behavior, but it's cheap so we do it anyway. 150 | if K.Equal(identity) == 1 { 151 | return nil, errors.New("cpace: invalid peer message") 152 | } 153 | 154 | h := hmac.New(sha256.New, transcript) 155 | h.Write(K.Encode(nil)) 156 | return h.Sum(nil), nil 157 | } 158 | 159 | // Finish processes the peer's response, generated by Exchange, and returns the 160 | // shared secret key. 161 | // 162 | // If the two peers agree on the password and ContextInfo, they will derive the 163 | // same key. Note that an error is NOT returned otherwise: the two peers will 164 | // simply derive different keys. 165 | // 166 | // The returned key is suitable to be passed to hkdf.Expand. 167 | func (s *State) Finish(msgB []byte) (key []byte, err error) { 168 | if len(msgB) != 32 { 169 | return nil, errors.New("cpace: invalid peer message") 170 | } 171 | 172 | transcript := append(s.transcript, msgB...) 173 | return deriveKey(msgB, transcript, s.secret) 174 | } 175 | 176 | // Exchange executes a PAKE exchange authenticated by password, processing msgA 177 | // generated by a peer with Start, and returns the shared secret key and msgB. 178 | // msgB should be sent to the peer, to be processed by (*State).Finish. 179 | // 180 | // If the two peers agree on the password and ContextInfo, they will derive the 181 | // same key. Note that an error is NOT returned otherwise: the two peers will 182 | // simply derive different keys. 183 | // 184 | // The returned key is suitable to be passed to hkdf.Expand. 185 | func Exchange(password string, c *ContextInfo, msgA []byte) (msgB, key []byte, err error) { 186 | if err := c.validate(); err != nil { 187 | return nil, nil, err 188 | } 189 | 190 | if len(msgA) != 16+32 { 191 | return nil, nil, errors.New("cpace: invalid peer message") 192 | } 193 | salt := msgA[:16] 194 | encodedA := msgA[16:] 195 | 196 | secret, err := randomScalar() 197 | if err != nil { 198 | return nil, nil, err 199 | } 200 | 201 | x := secretGenerator(password, salt, c) 202 | x.ScalarMult(secret, x) 203 | 204 | msgB = make([]byte, 0, 32) 205 | msgB = x.Encode(msgB) 206 | 207 | transcript := make([]byte, 0, 16+32+32) 208 | transcript = append(transcript, msgA...) 209 | transcript = append(transcript, msgB...) 210 | 211 | key, err = deriveKey(encodedA, transcript, secret) 212 | if err != nil { 213 | return nil, nil, err 214 | } 215 | 216 | return msgB, key, nil 217 | } 218 | -------------------------------------------------------------------------------- /cpace_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google LLC 2 | // 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd 6 | 7 | package cpace_test 8 | 9 | import ( 10 | "bytes" 11 | "crypto/rand" 12 | "crypto/sha256" 13 | "encoding/base64" 14 | "fmt" 15 | "io" 16 | "strings" 17 | "testing" 18 | 19 | "filippo.io/cpace" 20 | "github.com/gtank/ristretto255" 21 | "golang.org/x/crypto/hkdf" 22 | ) 23 | 24 | func Example() { 25 | password := "password" 26 | c := cpace.NewContextInfo("192.0.2.1:12345", "192.0.2.2:42", nil) 27 | 28 | msgA, s, err := cpace.Start(password, c) 29 | if err != nil { 30 | panic(err) 31 | } 32 | 33 | msgB, keyB, err := cpace.Exchange(password, c, msgA) 34 | if err != nil { 35 | panic(err) 36 | } 37 | 38 | keyA, err := s.Finish(msgB) 39 | if err != nil { 40 | panic(err) 41 | } 42 | 43 | fmt.Println("keyA == keyB:", bytes.Equal(keyA, keyB)) 44 | // Output: keyA == keyB: true 45 | } 46 | 47 | func TestTranscript(t *testing.T) { 48 | // Don't try this at home. 49 | defer func(original io.Reader) { rand.Reader = original }(rand.Reader) 50 | rand.Reader = hkdf.Expand(sha256.New, []byte("INSECURE"), nil) 51 | 52 | password := "password" 53 | c := cpace.NewContextInfo("a", "b", []byte("ad")) 54 | 55 | tx := sha256.New() 56 | 57 | msgA, s, err := cpace.Start(password, c) 58 | if err != nil { 59 | t.Fatal(err) 60 | } 61 | t.Logf("msgA: %x\n", msgA) 62 | tx.Write(msgA) 63 | 64 | msgB, key, err := cpace.Exchange(password, c, msgA) 65 | if err != nil { 66 | t.Fatal(err) 67 | } 68 | t.Logf("msgB: %x\n", msgB) 69 | tx.Write(msgB) 70 | t.Logf("key: %x\n", key) 71 | tx.Write(key) 72 | 73 | if keyA, err := s.Finish(msgB); err != nil { 74 | t.Fatal(err) 75 | } else if !bytes.Equal(key, keyA) { 76 | t.Error("keys were not equal") 77 | } 78 | 79 | expected := "NpHB1PcNLBGo9idbTXys5aRkuAlV+FQAshGfsJoxs3g" 80 | if h := base64.RawStdEncoding.EncodeToString(tx.Sum(nil)); h != expected { 81 | t.Errorf("transcript hash changed: got %q, expected %q", h, expected) 82 | } 83 | } 84 | 85 | func BenchmarkStart(b *testing.B) { 86 | password := "password" 87 | c := cpace.NewContextInfo("192.0.2.1:12345", "192.0.2.2:42", []byte("ad")) 88 | 89 | for i := 0; i < b.N; i++ { 90 | msgA, s, err := cpace.Start(password, c) 91 | if len(msgA) != 16+32 || s == nil || err != nil { 92 | panic(err) 93 | } 94 | } 95 | } 96 | 97 | func BenchmarkExchange(b *testing.B) { 98 | password := "password" 99 | c := cpace.NewContextInfo("192.0.2.1:12345", "192.0.2.2:42", []byte("ad")) 100 | 101 | msgA, _, err := cpace.Start(password, c) 102 | if err != nil { 103 | panic(err) 104 | } 105 | 106 | for i := 0; i < b.N; i++ { 107 | msgB, key, err := cpace.Exchange(password, c, msgA) 108 | if len(msgB) != 32 || len(key) != 32 || err != nil { 109 | panic(err) 110 | } 111 | } 112 | } 113 | 114 | func BenchmarkFinish(b *testing.B) { 115 | password := "password" 116 | c := cpace.NewContextInfo("192.0.2.1:12345", "192.0.2.2:42", []byte("ad")) 117 | 118 | msgA, s, err := cpace.Start(password, c) 119 | if err != nil { 120 | panic(err) 121 | } 122 | 123 | msgB, _, err := cpace.Exchange(password, c, msgA) 124 | if err != nil { 125 | panic(err) 126 | } 127 | 128 | for i := 0; i < b.N; i++ { 129 | key, err := s.Finish(msgB) 130 | if len(key) != 32 || err != nil { 131 | panic(err) 132 | } 133 | } 134 | } 135 | 136 | func TestLargeContextValues(t *testing.T) { 137 | password := "password" 138 | validC := cpace.NewContextInfo(strings.Repeat("a", 1<<16-1), "b", nil) 139 | badC := cpace.NewContextInfo(strings.Repeat("a", 1<<16), "b", nil) 140 | 141 | msgA, _, err := cpace.Start(password, validC) 142 | if err != nil { 143 | t.Fatal(err) 144 | } 145 | if _, _, err := cpace.Exchange(password, validC, msgA); err != nil { 146 | t.Fatal(err) 147 | } 148 | 149 | if _, _, err := cpace.Start(password, badC); err == nil { 150 | t.Error("expected error for long context value") 151 | } 152 | if _, _, err := cpace.Exchange(password, badC, msgA); err == nil { 153 | t.Error("expected error for long context value") 154 | } 155 | } 156 | 157 | func TestBrokenMessages(t *testing.T) { 158 | password := "password" 159 | c := cpace.NewContextInfo("192.0.2.1:12345", "192.0.2.2:42", nil) 160 | 161 | msgA, s, err := cpace.Start(password, c) 162 | if err != nil { 163 | t.Fatal(err) 164 | } 165 | 166 | if _, key, err := cpace.Exchange(password, c, msgA[:len(msgA)-1]); err == nil { 167 | t.Error("expected error for short msgA") 168 | } else if key != nil { 169 | t.Error("on error, key was not nil") 170 | } 171 | msgA[len(msgA)-1] ^= 0xff 172 | if _, key, err := cpace.Exchange(password, c, msgA[:len(msgA)-1]); err == nil { 173 | t.Error("expected error for modified msgA") 174 | } else if key != nil { 175 | t.Error("on error, key was not nil") 176 | } 177 | msgA[len(msgA)-1] ^= 0xff 178 | 179 | msgB, _, err := cpace.Exchange(password, c, msgA) 180 | if err != nil { 181 | t.Fatal(err) 182 | } 183 | 184 | if key, err := s.Finish(msgB[:len(msgB)-1]); err == nil { 185 | t.Error("expected error for short msgB") 186 | } else if key != nil { 187 | t.Error("on error, key was not nil") 188 | } 189 | msgB[len(msgB)-1] ^= 0xff 190 | if key, err := s.Finish(msgB[:len(msgB)-1]); err == nil { 191 | t.Error("expected error for modified msgB") 192 | } else if key != nil { 193 | t.Error("on error, key was not nil") 194 | } 195 | msgB[len(msgB)-1] ^= 0xff 196 | } 197 | 198 | func TestResults(t *testing.T) { 199 | tests := []struct { 200 | Name string 201 | PasswordA, PasswordB string 202 | ContextA, ContextB *cpace.ContextInfo 203 | Equal bool 204 | }{ 205 | { 206 | Name: "valid, without ad", Equal: true, 207 | PasswordA: "p", PasswordB: "p", 208 | ContextA: cpace.NewContextInfo("a", "b", nil), 209 | ContextB: cpace.NewContextInfo("a", "b", nil), 210 | }, 211 | { 212 | Name: "valid, with ad", Equal: true, 213 | PasswordA: "p", PasswordB: "p", 214 | ContextA: cpace.NewContextInfo("a", "b", []byte("x")), 215 | ContextB: cpace.NewContextInfo("a", "b", []byte("x")), 216 | }, 217 | { 218 | Name: "valid, equal identities", Equal: true, 219 | PasswordA: "p", PasswordB: "p", 220 | ContextA: cpace.NewContextInfo("a", "a", nil), 221 | ContextB: cpace.NewContextInfo("a", "a", nil), 222 | }, 223 | { 224 | Name: "different passwords", Equal: false, 225 | PasswordA: "p", PasswordB: "P", 226 | ContextA: cpace.NewContextInfo("a", "b", nil), 227 | ContextB: cpace.NewContextInfo("a", "b", nil), 228 | }, 229 | { 230 | Name: "different identity a", Equal: false, 231 | PasswordA: "p", PasswordB: "p", 232 | ContextA: cpace.NewContextInfo("a", "b", nil), 233 | ContextB: cpace.NewContextInfo("x", "b", nil), 234 | }, 235 | { 236 | Name: "different identity b", Equal: false, 237 | PasswordA: "p", PasswordB: "p", 238 | ContextA: cpace.NewContextInfo("a", "b", nil), 239 | ContextB: cpace.NewContextInfo("a", "x", nil), 240 | }, 241 | { 242 | Name: "different ad", Equal: false, 243 | PasswordA: "p", PasswordB: "p", 244 | ContextA: cpace.NewContextInfo("a", "b", []byte("foo")), 245 | ContextB: cpace.NewContextInfo("a", "b", []byte("bar")), 246 | }, 247 | { 248 | Name: "swapped identities", Equal: false, 249 | PasswordA: "p", PasswordB: "p", 250 | ContextA: cpace.NewContextInfo("a", "b", nil), 251 | ContextB: cpace.NewContextInfo("b", "a", nil), 252 | }, 253 | { 254 | Name: "missing ad", Equal: false, 255 | PasswordA: "p", PasswordB: "p", 256 | ContextA: cpace.NewContextInfo("a", "b", []byte("x")), 257 | ContextB: cpace.NewContextInfo("a", "b", nil), 258 | }, 259 | { 260 | Name: "identity concatenation", Equal: false, 261 | PasswordA: "p", PasswordB: "p", 262 | ContextA: cpace.NewContextInfo("ax", "b", nil), 263 | ContextB: cpace.NewContextInfo("a", "xb", nil), 264 | }, 265 | { 266 | Name: "empty password", Equal: false, 267 | PasswordA: "p", PasswordB: "", 268 | ContextA: cpace.NewContextInfo("a", "b", nil), 269 | ContextB: cpace.NewContextInfo("a", "b", nil), 270 | }, 271 | } 272 | for _, tt := range tests { 273 | t.Run(tt.Name, func(t *testing.T) { 274 | msgA, s, err := cpace.Start(tt.PasswordA, tt.ContextA) 275 | if err != nil { 276 | t.Fatal(err) 277 | } 278 | msgB, keyB, err := cpace.Exchange(tt.PasswordB, tt.ContextB, msgA) 279 | if err != nil { 280 | t.Fatal(err) 281 | } 282 | keyA, err := s.Finish(msgB) 283 | if err != nil { 284 | t.Fatal(err) 285 | } 286 | 287 | if len(keyA) != 32 { 288 | t.Errorf("keyA length is %v, expected %v", len(keyA), 32) 289 | } 290 | if len(keyB) != 32 { 291 | t.Errorf("keyB length is %v, expected %v", len(keyB), 32) 292 | } 293 | 294 | if eq := bytes.Equal(keyA, keyB); eq != tt.Equal { 295 | t.Errorf("keyA == keyB is %v, expected %v", eq, tt.Equal) 296 | } 297 | }) 298 | } 299 | } 300 | 301 | func TestIdentity(t *testing.T) { 302 | password := "password" 303 | c := cpace.NewContextInfo("192.0.2.1:12345", "192.0.2.2:42", nil) 304 | 305 | msgA, s, err := cpace.Start(password, c) 306 | if err != nil { 307 | t.Fatal(err) 308 | } 309 | 310 | identity := ristretto255.NewElement().Zero() 311 | 312 | if _, _, err := cpace.Exchange(password, c, identity.Encode(msgA[:16])); err == nil { 313 | t.Error("expected error for identity value") 314 | } 315 | if _, err := s.Finish(identity.Encode(nil)); err == nil { 316 | t.Error("expected error for identity value") 317 | } 318 | } 319 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module filippo.io/cpace 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/gtank/ristretto255 v0.1.2 7 | golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/gtank/ristretto255 v0.1.2 h1:JEqUCPA1NvLq5DwYtuzigd7ss8fwbYay9fi4/5uMzcc= 2 | github.com/gtank/ristretto255 v0.1.2/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIvY4OmlYW69o= 3 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 4 | golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 h1:3zb4D3T4G8jdExgVU/95+vQXfpEPiMdCaZgmGVxjNHM= 5 | golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 6 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 7 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 8 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 9 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 10 | --------------------------------------------------------------------------------