First cut of minimizing private_key exposure in the code base

Minor formatting
This commit is contained in:
Sarah Jamie Lewis 2017-12-05 11:00:04 -08:00
parent e1031861a2
commit 43b357fdb6
10 changed files with 113 additions and 41 deletions

View File

@ -128,6 +128,7 @@ func (crc *ContactRequestChannel) OpenOutboundResult(err error, crm *Protocol_Da
crc.channel.SendMessage([]byte{})
}
// SendResponse sends a contact request status response to the requester.
func (crc *ContactRequestChannel) SendResponse(status string) {
messageBuilder := new(utils.MessageBuilder)
crc.channel.SendMessage(messageBuilder.ReplyToContactRequest(crc.channel.ID, status))

View File

@ -8,6 +8,7 @@ import (
"crypto/sha256"
"encoding/asn1"
"github.com/golang/protobuf/proto"
"github.com/s-rah/go-ricochet/identity"
"github.com/s-rah/go-ricochet/utils"
"github.com/s-rah/go-ricochet/wire/auth"
"github.com/s-rah/go-ricochet/wire/control"
@ -15,14 +16,14 @@ import (
)
const (
// InvalidClientCookieError - returned when the client provides a cookie with the wrong length
InvalidClientCookieError = utils.Error("InvalidClientCookieError")
)
// HiddenServiceAuthChannel wraps implementation of im.ricochet.auth.hidden-service"
type HiddenServiceAuthChannel struct {
// PrivateKey must be set for client-side authentication channels
PrivateKey *rsa.PrivateKey
// Server Hostname must be set for client-side authentication channels
Identity identity.Identity
ServerHostname string
// Callbacks
@ -71,7 +72,7 @@ func (ah *HiddenServiceAuthChannel) Closed(err error) {
// Remote -> [Open Authentication Channel] -> Local
func (ah *HiddenServiceAuthChannel) OpenInbound(channel *Channel, oc *Protocol_Data_Control.OpenChannel) ([]byte, error) {
if ah.PrivateKey == nil {
if !ah.Identity.Initialized() {
return nil, utils.PrivateKeyNotSetError
}
@ -93,7 +94,7 @@ func (ah *HiddenServiceAuthChannel) OpenInbound(channel *Channel, oc *Protocol_D
// Local -> [Open Authentication Channel] -> Remote
func (ah *HiddenServiceAuthChannel) OpenOutbound(channel *Channel) ([]byte, error) {
if ah.PrivateKey == nil {
if !ah.Identity.Initialized() {
return nil, utils.PrivateKeyNotSetError
}
@ -122,15 +123,9 @@ func (ah *HiddenServiceAuthChannel) OpenOutboundResult(err error, crm *Protocol_
ah.AddServerCookie(serverCookie.([]byte)[:])
publicKeyBytes, _ := asn1.Marshal(rsa.PublicKey{
N: ah.PrivateKey.PublicKey.N,
E: ah.PrivateKey.PublicKey.E,
})
challenge := ah.GenChallenge(ah.Identity.Hostname(), ah.ServerHostname)
clientHostname := utils.GetTorHostname(publicKeyBytes)
challenge := ah.GenChallenge(clientHostname, ah.ServerHostname)
signature, err := rsa.SignPKCS1v15(nil, ah.PrivateKey, crypto.SHA256, challenge)
signature, err := ah.Identity.Sign(challenge)
if err != nil {
ah.channel.SendMessage([]byte{})
@ -138,14 +133,14 @@ func (ah *HiddenServiceAuthChannel) OpenOutboundResult(err error, crm *Protocol_
}
messageBuilder := new(utils.MessageBuilder)
proof := messageBuilder.Proof(publicKeyBytes, signature)
proof := messageBuilder.Proof(ah.Identity.PublicKeyBytes(), signature)
ah.channel.SendMessage(proof)
}
}
}
// Packet is called for each raw packet received on this channel.
// Input: Remote -> [Proof] -> Client
// Input: Client -> [Proof] -> Remote
// OR
// Input: Remote -> [Result] -> Client
func (ah *HiddenServiceAuthChannel) Packet(data []byte) {
@ -160,18 +155,13 @@ func (ah *HiddenServiceAuthChannel) Packet(data []byte) {
if res.GetProof() != nil && ah.channel.Direction == Inbound {
provisionalClientHostname := utils.GetTorHostname(res.GetProof().GetPublicKey())
publicKeyBytes, err := asn1.Marshal(rsa.PublicKey{
N: ah.PrivateKey.PublicKey.N,
E: ah.PrivateKey.PublicKey.E,
})
if err != nil {
ah.ServerAuthInvalid(err)
ah.channel.SendMessage([]byte{})
return
}
serverHostname := utils.GetTorHostname(publicKeyBytes)
serverHostname := ah.Identity.Hostname()
publicKey := rsa.PublicKey{}
_, err = asn1.Unmarshal(res.GetProof().GetPublicKey(), &publicKey)

View File

@ -4,6 +4,7 @@ import (
"bytes"
"crypto/rsa"
"github.com/golang/protobuf/proto"
"github.com/s-rah/go-ricochet/identity"
"github.com/s-rah/go-ricochet/utils"
"github.com/s-rah/go-ricochet/wire/control"
"testing"
@ -71,10 +72,10 @@ func GetOpenAuthenticationChannelMessage() *Protocol_Data_Control.OpenChannel {
}
func TestAuthenticationOpenInbound(t *testing.T) {
privateKey, _ := utils.LoadPrivateKeyFromFile("../testing/private_key")
id := identity.Init("../testing/private_key")
opm := GetOpenAuthenticationChannelMessage()
authHandler := new(HiddenServiceAuthChannel)
authHandler.PrivateKey = privateKey
authHandler.Identity = id
channel := Channel{ID: 1}
response, err := authHandler.OpenInbound(&channel, opm)
@ -86,14 +87,15 @@ func TestAuthenticationOpenInbound(t *testing.T) {
t.Errorf("Response not a Open Channel Result %v", res)
}
} else {
t.Errorf("HiddenServiceAuthChannel OpenOutbound Failed: %v", err)
t.Errorf("HiddenServiceAuthChannel OpenInbound Failed: %v", err)
}
}
func TestAuthenticationOpenOutbound(t *testing.T) {
privateKey, _ := utils.LoadPrivateKeyFromFile("../testing/private_key")
id := identity.Init("../testing/private_key")
authHandler := new(HiddenServiceAuthChannel)
authHandler.PrivateKey = privateKey
authHandler.Identity = id
authHandler.ServerHostname = "kwke2hntvyfqm7dr"
channel := Channel{ID: 1}
response, err := authHandler.OpenOutbound(&channel)
@ -105,20 +107,20 @@ func TestAuthenticationOpenOutbound(t *testing.T) {
t.Errorf("Open Channel Packet not included %v", res)
}
} else {
t.Errorf("HiddenServiceAuthChannel OpenInbound Failed: %v", err)
t.Errorf("HiddenServiceAuthChannel OpenOutbound Failed: %v", err)
}
}
func TestAuthenticationOpenOutboundResult(t *testing.T) {
privateKey, _ := utils.LoadPrivateKeyFromFile("../testing/private_key")
id := identity.Init("../testing/private_key")
authHandlerA := new(HiddenServiceAuthChannel)
authHandlerB := new(HiddenServiceAuthChannel)
authHandlerA.Identity = id
authHandlerA.ServerHostname = "kwke2hntvyfqm7dr"
authHandlerA.PrivateKey = privateKey
authHandlerA.ClientAuthResult = func(accepted, known bool) {}
channelA := Channel{ID: 1, Direction: Outbound}
channelA.SendMessage = func(message []byte) {
@ -130,8 +132,7 @@ func TestAuthenticationOpenOutboundResult(t *testing.T) {
res := new(Protocol_Data_Control.Packet)
proto.Unmarshal(response[:], res)
authHandlerB.ServerHostname = "kwke2hntvyfqm7dr"
authHandlerB.PrivateKey = privateKey
authHandlerB.Identity = id
authHandlerB.ServerAuthValid = func(hostname string, publicKey rsa.PublicKey) (allowed, known bool) { return true, true }
authHandlerB.ServerAuthInvalid = func(err error) { t.Error("server received invalid auth") }
channelB := Channel{ID: 1, Direction: Inbound}

View File

@ -84,6 +84,9 @@ func NewOutboundConnection(conn io.ReadWriteCloser, remoteHostname string) *Conn
return rc
}
// TraceLog turns on debug logging, you shouldn't need to do this but if for some
// reason ricochet isn't working, you can use this to see at what point in the
// protcol trace ricochet is failing.
func (rc *Connection) TraceLog(enabled bool) {
rc.trace = enabled
}

View File

@ -3,6 +3,7 @@ package connection
import (
"crypto/rsa"
"github.com/s-rah/go-ricochet/channels"
"github.com/s-rah/go-ricochet/identity"
"github.com/s-rah/go-ricochet/policies"
"github.com/s-rah/go-ricochet/utils"
"sync"
@ -63,7 +64,7 @@ func (ich *InboundConnectionHandler) ProcessAuthAsServer(privateKey *rsa.Private
ach.RegisterChannelHandler("im.ricochet.auth.hidden-service",
func() channels.Handler {
return &channels.HiddenServiceAuthChannel{
PrivateKey: privateKey,
Identity: identity.Initialize("", privateKey),
ServerAuthValid: onAuthValid,
ServerAuthInvalid: onAuthInvalid,
}

View File

@ -3,6 +3,7 @@ package connection
import (
"crypto/rsa"
"github.com/s-rah/go-ricochet/channels"
"github.com/s-rah/go-ricochet/identity"
"github.com/s-rah/go-ricochet/policies"
"github.com/s-rah/go-ricochet/utils"
"sync"
@ -68,7 +69,7 @@ func (och *OutboundConnectionHandler) ProcessAuthAsClient(privateKey *rsa.Privat
err := och.connection.Do(func() error {
_, err := och.connection.RequestOpenChannel("im.ricochet.auth.hidden-service",
&channels.HiddenServiceAuthChannel{
PrivateKey: privateKey,
Identity: identity.Initialize("", privateKey),
ServerHostname: och.connection.RemoteHostname,
ClientAuthResult: authCallback,
})

62
identity/identity.go Normal file
View File

@ -0,0 +1,62 @@
package identity
import (
"crypto"
"crypto/rsa"
"encoding/asn1"
"github.com/s-rah/go-ricochet/utils"
)
// Identity is an encapsulation of Name, PrivateKey and other features
// that make up a Ricochet client.
// The purpose of Identity is to prevent other classes directly accessing private key
// and to ensure the integrity of security-critical functions.
type Identity struct {
Name string
pk *rsa.PrivateKey
}
// Init loads an identity from a file. Currently file should be a private_key
// but this may change in the future. //XXX
func Init(filename string) Identity {
pk, err := utils.LoadPrivateKeyFromFile(filename)
if err == nil {
return Identity{"", pk}
}
return Identity{}
}
// Initialize is a courtesy function for initializing an Identity in-code.
func Initialize(name string, pk *rsa.PrivateKey) Identity {
return Identity{name, pk}
}
// Initialized ensures that an Identity has been assigned a private_key and
// is ready to perform operations.
func (i *Identity) Initialized() bool {
if i.pk == nil {
return false
}
return true
}
// PublicKeyBytes returns the public key associated with this Identity in serializable-friendly
// format. //TODO Not sure I like this.
func (i *Identity) PublicKeyBytes() []byte {
publicKeyBytes, _ := asn1.Marshal(rsa.PublicKey{
N: i.pk.PublicKey.N,
E: i.pk.PublicKey.E,
})
return publicKeyBytes
}
// Hostname provides the onion address associated with this Identity.
func (i *Identity) Hostname() string {
return utils.GetTorHostname(i.PublicKeyBytes())
}
// Sign produces a cryptographic signature using this Identities private key.
func (i *Identity) Sign(challenge []byte) ([]byte, error) {
return rsa.SignPKCS1v15(nil, i.pk, crypto.SHA256, challenge)
}

13
identity/identity_test.go Normal file
View File

@ -0,0 +1,13 @@
package identity
import (
"github.com/s-rah/go-ricochet/identity"
"testing"
)
func TestIdentity(t *testing.T) {
id := identity.Init("../testing/private_key")
if id.Hostname() != "kwke2hntvyfqm7dr" {
t.Errorf("Expected %v as Hostname() got: ", "kwke2hntvyfqm7dr", id.Hostname())
}
}

View File

@ -28,7 +28,7 @@ func Open(remoteHostname string) (*connection.Connection, error) {
return rc, nil
}
// negotiate version takes an open network connection and executes
// NegotiateVersionOutbound takes an open network connection and executes
// the ricochet version negotiation procedure.
func NegotiateVersionOutbound(conn net.Conn, remoteHostname string) (*connection.Connection, error) {
versions := []byte{0x49, 0x4D, 0x01, 0x01}

View File

@ -5,15 +5,15 @@ import (
)
func TestGeneratePrivateKey(t *testing.T) {
_, err := GeneratePrivateKey()
if err != nil {
t.Errorf("Error while generating private key: %v", err)
}
_, err := GeneratePrivateKey()
if err != nil {
t.Errorf("Error while generating private key: %v", err)
}
}
func TestLoadPrivateKey(t *testing.T) {
_,err := LoadPrivateKeyFromFile("../testing/private_key")
if err != nil {
t.Errorf("Error while loading private key from file: %v", err)
}
_, err := LoadPrivateKeyFromFile("../testing/private_key")
if err != nil {
t.Errorf("Error while loading private key from file: %v", err)
}
}