Tapir provides a framework for building Anonymous / metadata resistant Services
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

auth.go 3.1KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. package applications
  2. import (
  3. "crypto/subtle"
  4. "cwtch.im/tapir"
  5. "cwtch.im/tapir/primitives"
  6. "encoding/json"
  7. "git.openprivacy.ca/openprivacy/libricochet-go/log"
  8. "git.openprivacy.ca/openprivacy/libricochet-go/utils"
  9. "golang.org/x/crypto/ed25519"
  10. "golang.org/x/crypto/sha3"
  11. "time"
  12. )
  13. // AuthMessage is exchanged between peers to obtain the Auth Capability
  14. type AuthMessage struct {
  15. LongTermPublicKey ed25519.PublicKey
  16. EphemeralPublicKey ed25519.PublicKey
  17. }
  18. // AuthCapability defines the Authentication Capability granted by AuthApp
  19. const AuthCapability = "AUTH"
  20. // AuthApp is the concrete Application type that handles Authentication
  21. type AuthApp struct {
  22. }
  23. // NewInstance creates a new instance of the AuthApp
  24. func (ea AuthApp) NewInstance() tapir.Application {
  25. return new(AuthApp)
  26. }
  27. // Init runs the entire AuthApp protocol, at the end of the protocol either the connection is granted AUTH capability
  28. // or the connection is closed.
  29. func (ea AuthApp) Init(connection tapir.Connection) {
  30. longTermPubKey := ed25519.PublicKey(connection.ID().PublicKeyBytes())
  31. ephemeralIdentity, _ := primitives.InitializeEphemeral()
  32. authMessage := AuthMessage{LongTermPublicKey: longTermPubKey, EphemeralPublicKey: ephemeralIdentity.PublicKey()}
  33. serialized, _ := json.Marshal(authMessage)
  34. connection.Send(serialized)
  35. message := connection.Expect()
  36. var remoteAuthMessage AuthMessage
  37. err := json.Unmarshal(message, &remoteAuthMessage)
  38. if err != nil {
  39. connection.Close()
  40. return
  41. }
  42. key := primitives.Perform3DH(connection.ID(), &ephemeralIdentity, remoteAuthMessage.LongTermPublicKey, remoteAuthMessage.EphemeralPublicKey, connection.IsOutbound())
  43. connection.SetEncryptionKey(key)
  44. // Wait to Sync (we need to ensure that both the Local and Remote server have turned encryption on
  45. // otherwise our next Send will fail.
  46. time.Sleep(time.Second)
  47. // TODO: Replace this with proper transcript primitive
  48. challengeRemote, err := json.Marshal(remoteAuthMessage)
  49. if err != nil {
  50. connection.Close()
  51. return
  52. }
  53. challengeLocal, err := json.Marshal(authMessage)
  54. if err != nil {
  55. connection.Close()
  56. return
  57. }
  58. challenge := sha3.New512()
  59. if connection.IsOutbound() {
  60. challenge.Write(challengeLocal)
  61. challenge.Write(challengeRemote)
  62. } else {
  63. challenge.Write(challengeRemote)
  64. challenge.Write(challengeLocal)
  65. }
  66. // Since we have set the encryption key on the connection the connection will encrypt any messages we send with that key
  67. // To test that the remote peer has done the same we calculate a challenge hash based on the transcript so far and send it to them
  68. // We expect the remote to do the same, and compare the two.
  69. // If successful we extend our auth capability to the connection and reassert the hostname.
  70. challengeBytes := challenge.Sum([]byte{})
  71. connection.Send(challengeBytes)
  72. remoteChallenge := connection.Expect()
  73. if subtle.ConstantTimeCompare(challengeBytes, remoteChallenge) == 1 {
  74. connection.SetHostname(utils.GetTorV3Hostname(remoteAuthMessage.LongTermPublicKey))
  75. connection.SetCapability(AuthCapability)
  76. } else {
  77. log.Errorf("Failed Decrypt Challenge: [%x] [%x]\n", remoteChallenge, challengeBytes)
  78. connection.Close()
  79. }
  80. }