github.com/keltia/go-ipfs@v0.3.8-0.20150909044612-210793031c63/p2p/crypto/secio/protocol.go (about) 1 package secio 2 3 import ( 4 "bytes" 5 "crypto/rand" 6 "errors" 7 "fmt" 8 "io" 9 "sync" 10 "time" 11 12 msgio "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-msgio" 13 context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context" 14 ci "github.com/ipfs/go-ipfs/p2p/crypto" 15 pb "github.com/ipfs/go-ipfs/p2p/crypto/secio/pb" 16 peer "github.com/ipfs/go-ipfs/p2p/peer" 17 eventlog "github.com/ipfs/go-ipfs/thirdparty/eventlog" 18 u "github.com/ipfs/go-ipfs/util" 19 ) 20 21 var log = eventlog.Logger("secio") 22 23 // ErrUnsupportedKeyType is returned when a private key cast/type switch fails. 24 var ErrUnsupportedKeyType = errors.New("unsupported key type") 25 26 // ErrClosed signals the closing of a connection. 27 var ErrClosed = errors.New("connection closed") 28 29 // ErrEcho is returned when we're attempting to handshake with the same keys and nonces. 30 var ErrEcho = errors.New("same keys and nonces. one side talking to self.") 31 32 // HandshakeTimeout governs how long the handshake will be allowed to take place for. 33 // Making this number large means there could be many bogus connections waiting to 34 // timeout in flight. Typical handshakes take ~3RTTs, so it should be completed within 35 // seconds across a typical planet in the solar system. 36 var HandshakeTimeout = time.Second * 30 37 38 // nonceSize is the size of our nonces (in bytes) 39 const nonceSize = 16 40 41 // secureSession encapsulates all the parameters needed for encrypting 42 // and decrypting traffic from an insecure channel. 43 type secureSession struct { 44 ctx context.Context 45 cancel context.CancelFunc 46 47 secure msgio.ReadWriteCloser 48 insecure io.ReadWriteCloser 49 insecureM msgio.ReadWriter 50 51 localKey ci.PrivKey 52 localPeer peer.ID 53 remotePeer peer.ID 54 55 local encParams 56 remote encParams 57 58 sharedSecret []byte 59 60 handshakeMu sync.Mutex // guards handshakeDone + handshakeErr 61 handshakeDone bool 62 handshakeErr error 63 } 64 65 func (s *secureSession) Loggable() map[string]interface{} { 66 m := make(map[string]interface{}) 67 m["localPeer"] = s.localPeer.Pretty() 68 m["remotePeer"] = s.remotePeer.Pretty() 69 m["established"] = (s.secure != nil) 70 return m 71 } 72 73 func newSecureSession(ctx context.Context, local peer.ID, key ci.PrivKey, insecure io.ReadWriteCloser) (*secureSession, error) { 74 s := &secureSession{localPeer: local, localKey: key} 75 s.ctx, s.cancel = context.WithCancel(ctx) 76 77 switch { 78 case s.localPeer == "": 79 return nil, errors.New("no local id provided") 80 case s.localKey == nil: 81 return nil, errors.New("no local private key provided") 82 case !s.localPeer.MatchesPrivateKey(s.localKey): 83 return nil, fmt.Errorf("peer.ID does not match PrivateKey") 84 case insecure == nil: 85 return nil, fmt.Errorf("insecure ReadWriter is nil") 86 } 87 88 s.ctx = ctx 89 s.insecure = insecure 90 s.insecureM = msgio.NewReadWriter(insecure) 91 return s, nil 92 } 93 94 func (s *secureSession) Handshake() error { 95 s.handshakeMu.Lock() 96 defer s.handshakeMu.Unlock() 97 98 if s.handshakeErr != nil { 99 return s.handshakeErr 100 } 101 102 if !s.handshakeDone { 103 s.handshakeErr = s.runHandshake() 104 s.handshakeDone = true 105 } 106 return s.handshakeErr 107 } 108 109 // runHandshake performs initial communication over insecure channel to share 110 // keys, IDs, and initiate communication, assigning all necessary params. 111 // requires the duplex channel to be a msgio.ReadWriter (for framed messaging) 112 func (s *secureSession) runHandshake() error { 113 ctx, cancel := context.WithTimeout(s.ctx, HandshakeTimeout) // remove 114 defer cancel() 115 116 // ============================================================================= 117 // step 1. Propose -- propose cipher suite + send pubkeys + nonce 118 119 // Generate and send Hello packet. 120 // Hello = (rand, PublicKey, Supported) 121 nonceOut := make([]byte, nonceSize) 122 _, err := rand.Read(nonceOut) 123 if err != nil { 124 return err 125 } 126 127 defer log.EventBegin(ctx, "secureHandshake", s).Done() 128 129 s.local.permanentPubKey = s.localKey.GetPublic() 130 myPubKeyBytes, err := s.local.permanentPubKey.Bytes() 131 if err != nil { 132 return err 133 } 134 135 proposeOut := new(pb.Propose) 136 proposeOut.Rand = nonceOut 137 proposeOut.Pubkey = myPubKeyBytes 138 proposeOut.Exchanges = &SupportedExchanges 139 proposeOut.Ciphers = &SupportedCiphers 140 proposeOut.Hashes = &SupportedHashes 141 142 // log.Debugf("1.0 Propose: nonce:%s exchanges:%s ciphers:%s hashes:%s", 143 // nonceOut, SupportedExchanges, SupportedCiphers, SupportedHashes) 144 145 // Send Propose packet (respects ctx) 146 proposeOutBytes, err := writeMsgCtx(ctx, s.insecureM, proposeOut) 147 if err != nil { 148 return err 149 } 150 151 // Receive + Parse their Propose packet and generate an Exchange packet. 152 proposeIn := new(pb.Propose) 153 proposeInBytes, err := readMsgCtx(ctx, s.insecureM, proposeIn) 154 if err != nil { 155 return err 156 } 157 158 // log.Debugf("1.0.1 Propose recv: nonce:%s exchanges:%s ciphers:%s hashes:%s", 159 // proposeIn.GetRand(), proposeIn.GetExchanges(), proposeIn.GetCiphers(), proposeIn.GetHashes()) 160 161 // ============================================================================= 162 // step 1.1 Identify -- get identity from their key 163 164 // get remote identity 165 s.remote.permanentPubKey, err = ci.UnmarshalPublicKey(proposeIn.GetPubkey()) 166 if err != nil { 167 return err 168 } 169 170 // get peer id 171 s.remotePeer, err = peer.IDFromPublicKey(s.remote.permanentPubKey) 172 if err != nil { 173 return err 174 } 175 176 log.Debugf("1.1 Identify: %s Remote Peer Identified as %s", s.localPeer, s.remotePeer) 177 178 // ============================================================================= 179 // step 1.2 Selection -- select/agree on best encryption parameters 180 181 // to determine order, use cmp(H(remote_pubkey||local_rand), H(local_pubkey||remote_rand)). 182 oh1 := u.Hash(append(proposeIn.GetPubkey(), nonceOut...)) 183 oh2 := u.Hash(append(myPubKeyBytes, proposeIn.GetRand()...)) 184 order := bytes.Compare(oh1, oh2) 185 if order == 0 { 186 return ErrEcho // talking to self (same socket. must be reuseport + dialing self) 187 } 188 189 s.local.curveT, err = selectBest(order, SupportedExchanges, proposeIn.GetExchanges()) 190 if err != nil { 191 return err 192 } 193 194 s.local.cipherT, err = selectBest(order, SupportedCiphers, proposeIn.GetCiphers()) 195 if err != nil { 196 return err 197 } 198 199 s.local.hashT, err = selectBest(order, SupportedHashes, proposeIn.GetHashes()) 200 if err != nil { 201 return err 202 } 203 204 // we use the same params for both directions (must choose same curve) 205 // WARNING: if they dont SelectBest the same way, this won't work... 206 s.remote.curveT = s.local.curveT 207 s.remote.cipherT = s.local.cipherT 208 s.remote.hashT = s.local.hashT 209 210 // log.Debugf("1.2 selection: exchange:%s cipher:%s hash:%s", 211 // s.local.curveT, s.local.cipherT, s.local.hashT) 212 213 // ============================================================================= 214 // step 2. Exchange -- exchange (signed) ephemeral keys. verify signatures. 215 216 // Generate EphemeralPubKey 217 var genSharedKey ci.GenSharedKey 218 s.local.ephemeralPubKey, genSharedKey, err = ci.GenerateEKeyPair(s.local.curveT) 219 220 // Gather corpus to sign. 221 selectionOut := new(bytes.Buffer) 222 selectionOut.Write(proposeOutBytes) 223 selectionOut.Write(proposeInBytes) 224 selectionOut.Write(s.local.ephemeralPubKey) 225 selectionOutBytes := selectionOut.Bytes() 226 227 // log.Debugf("2.0 exchange: %v", selectionOutBytes) 228 exchangeOut := new(pb.Exchange) 229 exchangeOut.Epubkey = s.local.ephemeralPubKey 230 exchangeOut.Signature, err = s.localKey.Sign(selectionOutBytes) 231 if err != nil { 232 return err 233 } 234 235 // Send Propose packet (respects ctx) 236 if _, err := writeMsgCtx(ctx, s.insecureM, exchangeOut); err != nil { 237 return err 238 } 239 240 // Receive + Parse their Exchange packet. 241 exchangeIn := new(pb.Exchange) 242 if _, err := readMsgCtx(ctx, s.insecureM, exchangeIn); err != nil { 243 return err 244 } 245 246 // ============================================================================= 247 // step 2.1. Verify -- verify their exchange packet is good. 248 249 // get their ephemeral pub key 250 s.remote.ephemeralPubKey = exchangeIn.GetEpubkey() 251 252 selectionIn := new(bytes.Buffer) 253 selectionIn.Write(proposeInBytes) 254 selectionIn.Write(proposeOutBytes) 255 selectionIn.Write(s.remote.ephemeralPubKey) 256 selectionInBytes := selectionIn.Bytes() 257 // log.Debugf("2.0.1 exchange recv: %v", selectionInBytes) 258 259 // u.POut("Remote Peer Identified as %s\n", s.remote) 260 sigOK, err := s.remote.permanentPubKey.Verify(selectionInBytes, exchangeIn.GetSignature()) 261 if err != nil { 262 // log.Error("2.1 Verify: failed: %s", err) 263 return err 264 } 265 266 if !sigOK { 267 err := errors.New("Bad signature!") 268 // log.Error("2.1 Verify: failed: %s", err) 269 return err 270 } 271 // log.Debugf("2.1 Verify: signature verified.") 272 273 // ============================================================================= 274 // step 2.2. Keys -- generate keys for mac + encryption 275 276 // OK! seems like we're good to go. 277 s.sharedSecret, err = genSharedKey(exchangeIn.GetEpubkey()) 278 if err != nil { 279 return err 280 } 281 282 // generate two sets of keys (stretching) 283 k1, k2 := ci.KeyStretcher(s.local.cipherT, s.local.hashT, s.sharedSecret) 284 285 // use random nonces to decide order. 286 switch { 287 case order > 0: 288 // just break 289 case order < 0: 290 k1, k2 = k2, k1 // swap 291 default: 292 // we should've bailed before this. but if not, bail here. 293 return ErrEcho 294 } 295 s.local.keys = k1 296 s.remote.keys = k2 297 298 // log.Debug("2.2 keys:\n\tshared: %v\n\tk1: %v\n\tk2: %v", 299 // s.sharedSecret, s.local.keys, s.remote.keys) 300 301 // ============================================================================= 302 // step 2.3. MAC + Cipher -- prepare MAC + cipher 303 304 if err := s.local.makeMacAndCipher(); err != nil { 305 return err 306 } 307 308 if err := s.remote.makeMacAndCipher(); err != nil { 309 return err 310 } 311 312 // log.Debug("2.3 mac + cipher.") 313 314 // ============================================================================= 315 // step 3. Finish -- send expected message to verify encryption works (send local nonce) 316 317 // setup ETM ReadWriter 318 w := NewETMWriter(s.insecure, s.local.cipher, s.local.mac) 319 r := NewETMReader(s.insecure, s.remote.cipher, s.remote.mac) 320 s.secure = msgio.Combine(w, r).(msgio.ReadWriteCloser) 321 322 // log.Debug("3.0 finish. sending: %v", proposeIn.GetRand()) 323 // send their Nonce. 324 if _, err := s.secure.Write(proposeIn.GetRand()); err != nil { 325 return fmt.Errorf("Failed to write Finish nonce: %s", err) 326 } 327 328 // read our Nonce 329 nonceOut2 := make([]byte, len(nonceOut)) 330 if _, err := io.ReadFull(s.secure, nonceOut2); err != nil { 331 return fmt.Errorf("Failed to read Finish nonce: %s", err) 332 } 333 334 // log.Debug("3.0 finish.\n\texpect: %v\n\tactual: %v", nonceOut, nonceOut2) 335 if !bytes.Equal(nonceOut, nonceOut2) { 336 return fmt.Errorf("Failed to read our encrypted nonce: %s != %s", nonceOut2, nonceOut) 337 } 338 339 // Whew! ok, that's all folks. 340 return nil 341 }