Example #1
0
// RecvActThree processes the final act (act three) sent from the initiator to
// the responder. After processing this act, the responder learns of the
// initiators's static public key. Decryption of the static key serves to
// authenticate the initiator to the responder.
func (b *BrontideMachine) RecvActThree(actThree [ActThreeSize]byte) error {
	var (
		err error
		s   [33 + 16]byte
		p   [16]byte
	)

	copy(s[:], actThree[:33+16])
	copy(p[:], actThree[33+16:])

	// s
	remotePub, err := b.DecryptAndHash(s[:])
	if err != nil {
		return err
	}
	b.remoteStatic, err = btcec.ParsePubKey(remotePub, btcec.S256())
	if err != nil {
		return err
	}

	// se
	se := btcec.GenerateSharedSecret(b.localEphemeral, b.remoteStatic)
	b.mixKey(se)

	if _, err := b.DecryptAndHash(p[:]); err != nil {
		return err
	}

	// With the final ECDH operation complete, derive the session sending
	// and receiving keys.
	b.split()

	return nil
}
Example #2
0
// RecvActTwo processes the second packet (act two) sent from the responder to
// the initiator. A succesful processing of this packet authenticates the
// initiator to the responder.
func (b *BrontideMachine) RecvActTwo(actTwo [ActTwoSize]byte) error {
	var (
		err error
		e   [33]byte
		p   [16]byte
	)

	copy(e[:], actTwo[:33])
	copy(p[:], actTwo[33:])

	// e
	b.remoteEphemeral, err = btcec.ParsePubKey(e[:], btcec.S256())
	if err != nil {
		return err
	}
	b.mixHash(b.remoteEphemeral.SerializeCompressed())

	// ee
	s := btcec.GenerateSharedSecret(b.localEphemeral, b.remoteEphemeral)
	b.mixKey(s)

	if _, err := b.DecryptAndHash(p[:]); err != nil {
		return err
	}

	return nil
}
Example #3
0
// GenActTwo generates the second packet (act two) to be sent from the
// responder to the initiator. The packet for act two is identify to that of
// act one, but then results in a different ECDH operation between the
// initiator's and responder's ephemeral keys.
//
//    <- e, ee
func (b *BrontideMachine) GenActTwo() ([ActTwoSize]byte, error) {
	var (
		err    error
		actTwo [ActTwoSize]byte
	)

	// e
	b.localEphemeral, err = btcec.NewPrivateKey(btcec.S256())
	if err != nil {
		return actTwo, err
	}

	ephemeral := b.localEphemeral.PubKey().SerializeCompressed()
	b.mixHash(b.localEphemeral.PubKey().SerializeCompressed())

	// ee
	s := btcec.GenerateSharedSecret(b.localEphemeral, b.remoteEphemeral)
	b.mixKey(s)

	authPayload := b.EncryptAndHash([]byte{})

	copy(actTwo[:33], ephemeral)
	copy(actTwo[33:], authPayload)

	return actTwo, nil
}
Example #4
0
// RecvActOne processes the act one packet sent by the initiator. The responder
// executes the mirroed actions to that of the initiator extending the
// handshake digest and deriving a new shared secret based on a ECDH with the
// initiator's ephemeral key and reponder's static key.
func (b *BrontideMachine) RecvActOne(actOne [ActOneSize]byte) error {
	var (
		err error
		e   [33]byte
		p   [16]byte
	)

	copy(e[:], actOne[:33])
	copy(p[:], actOne[33:])

	// e
	b.remoteEphemeral, err = btcec.ParsePubKey(e[:], btcec.S256())
	if err != nil {
		return err
	}
	b.mixHash(b.remoteEphemeral.SerializeCompressed())

	// es
	s := btcec.GenerateSharedSecret(b.localStatic, b.remoteEphemeral)
	b.mixKey(s)

	// If the initiator doesn't know our static key, then this operation
	// will fail.
	if _, err := b.DecryptAndHash(p[:]); err != nil {
		return err
	}

	return nil
}
Example #5
0
// GenActThree creates the final (act three) packet of the handshake. Act three
// is to be sent from the initiator to the responder. The purpose of act three
// is to transmit the initiator's public key under strong forwad secrecy to the
// responder. This act also includes the final ECDH operation which yields the
// final session.
//
//    -> s, se
func (b *BrontideMachine) GenActThree() ([ActThreeSize]byte, error) {
	var actThree [ActThreeSize]byte

	ourPubkey := b.localStatic.PubKey().SerializeCompressed()
	ciphertext := b.EncryptAndHash(ourPubkey)

	s := btcec.GenerateSharedSecret(b.localStatic, b.remoteEphemeral)
	b.mixKey(s)

	authPayload := b.EncryptAndHash([]byte{})

	copy(actThree[:49], ciphertext)
	copy(actThree[49:], authPayload)

	// With the final ECDH operation complete, derive the session sending
	// and receiving keys.
	b.split()

	return actThree, nil
}
// NewMixHeader creates a new mix header which is capable of
// obliviously routing a message through the mix-net path outline by
// 'paymentPath'.  This function returns the created mix header along
// with a derived shared secret for each node in the path.
func NewMixHeader(paymentPath []*btcec.PublicKey, sessionKey *btcec.PrivateKey,
	rawHopPayloads [][]byte, assocData []byte) (*MixHeader,
	[][sharedSecretSize]byte, error) {

	// Each hop performs ECDH with our ephemeral key pair to arrive at a
	// shared secret. Additionally, each hop randomizes the group element
	// for the next hop by multiplying it by the blinding factor. This way
	// we only need to transmit a single group element, and hops can't link
	// a session back to us if they have several nodes in the path.
	numHops := len(paymentPath)
	hopEphemeralPubKeys := make([]*btcec.PublicKey, numHops)
	hopSharedSecrets := make([][sha256.Size]byte, numHops)
	hopBlindingFactors := make([][sha256.Size]byte, numHops)

	// Compute the triplet for the first hop outside of the main loop.
	// Within the loop each new triplet will be computed recursively based
	// off of the blinding factor of the last hop.
	hopEphemeralPubKeys[0] = sessionKey.PubKey()
	hopSharedSecrets[0] = sha256.Sum256(btcec.GenerateSharedSecret(sessionKey, paymentPath[0]))
	hopBlindingFactors[0] = computeBlindingFactor(hopEphemeralPubKeys[0], hopSharedSecrets[0][:])

	// Now recursively compute the ephemeral ECDH pub keys, the shared
	// secret, and blinding factor for each hop.
	for i := 1; i <= numHops-1; i++ {
		// a_{n} = a_{n-1} x c_{n-1} -> (Y_prev_pub_key x prevBlindingFactor)
		hopEphemeralPubKeys[i] = blindGroupElement(hopEphemeralPubKeys[i-1],
			hopBlindingFactors[i-1][:])

		// s_{n} = sha256( y_{n} x c_{n-1} ) ->
		// (Y_their_pub_key x x_our_priv) x all prev blinding factors
		yToX := blindGroupElement(paymentPath[i], sessionKey.D.Bytes())
		hopSharedSecrets[i] = sha256.Sum256(multiScalarMult(yToX, hopBlindingFactors[:i]).X.Bytes())

		// TODO(roasbeef): prob don't need to store all blinding factors, only the prev...
		// b_{n} = sha256(a_{n} || s_{n})
		hopBlindingFactors[i] = computeBlindingFactor(hopEphemeralPubKeys[i],
			hopSharedSecrets[i][:])

	}

	// Generate the padding, called "filler strings" in the paper.
	filler := generateHeaderPadding("rho", numHops, 2*securityParameter, hopSharedSecrets)
	hopFiller := generateHeaderPadding("gamma", numHops, HopPayloadSize, hopSharedSecrets)

	// Allocate and initialize fields to zero-filled slices
	var mixHeader [routingInfoSize]byte
	var hopPayloads [NumMaxHops * HopPayloadSize]byte

	// Same goes for the HMAC
	var next_hmac [20]byte
	next_address := bytes.Repeat([]byte{0x00}, 20)

	// Now we compute the routing information for each hop, along with a
	// MAC of the routing info using the shared key for that hop.
	for i := numHops - 1; i >= 0; i-- {

		rhoKey := generateKey("rho", hopSharedSecrets[i])
		gammaKey := generateKey("gamma", hopSharedSecrets[i])
		muKey := generateKey("mu", hopSharedSecrets[i])

		// Shift and obfuscate routing info
		streamBytes := generateCipherStream(rhoKey, numStreamBytes)
		rightShift(mixHeader[:], 2*securityParameter)
		copy(mixHeader[:], next_address[:])
		copy(mixHeader[securityParameter:], next_hmac[:])
		xor(mixHeader[:], mixHeader[:], streamBytes[:routingInfoSize])

		// Shift and obfuscate per-hop payload
		rightShift(hopPayloads[:], HopPayloadSize)
		copy(hopPayloads[:], rawHopPayloads[i])
		hopStreamBytes := generateCipherStream(gammaKey, uint(len(hopPayloads)))
		xor(hopPayloads[:], hopPayloads[:], hopStreamBytes)

		// We need to overwrite these so every node generates a correct padding
		if i == numHops-1 {
			copy(mixHeader[len(mixHeader)-len(filler):], filler)
			copy(hopPayloads[len(hopPayloads)-len(hopFiller):], hopFiller)
		}

		packet := append(append(mixHeader[:], hopPayloads[:]...), assocData...)
		next_hmac = calcMac(muKey, packet)
		next_address = btcutil.Hash160(paymentPath[i].SerializeCompressed())
	}

	header := &MixHeader{
		Version:      0x01,
		EphemeralKey: hopEphemeralPubKeys[0],
		RoutingInfo:  mixHeader,
		HeaderMAC:    next_hmac,
		HopPayload:   hopPayloads,
	}
	return header, hopSharedSecrets, nil
}
// ProcessOnionPacket processes an incoming onion packet which has been forward
// to the target Sphinx router. If the encoded ephemeral key isn't on the
// target Elliptic Curve, then the packet is rejected. Similarly, if the
// derived shared secret has been seen before the packet is rejected.  Finally
// if the MAC doesn't check the packet is again rejected.
//
// In the case of a successful packet processing, and ProcessedPacket struct is
// returned which houses the newly parsed packet, along with instructions on
// what to do next.
func (r *Router) ProcessOnionPacket(onionPkt *OnionPacket, assocData []byte) (*ProcessedPacket, error) {
	mixHeader := onionPkt.Header

	dhKey := mixHeader.EphemeralKey
	routeInfo := mixHeader.RoutingInfo
	headerMac := mixHeader.HeaderMAC
	var hopPayload [HopPayloadSize]byte

	// Ensure that the public key is on our curve.
	if !r.onionKey.Curve.IsOnCurve(dhKey.X, dhKey.Y) {
		return nil, fmt.Errorf("pubkey isn't on secp256k1 curve")
	}

	// Compute our shared secret.
	sharedSecret := sha256.Sum256(btcec.GenerateSharedSecret(r.onionKey, dhKey))

	// In order to mitigate replay attacks, if we've seen this particular
	// shared secret before, cease processing and just drop this forwarding
	// message.
	r.RLock()
	if _, ok := r.seenSecrets[sharedSecret]; ok {
		r.RUnlock()
		return nil, ErrReplayedPacket
	}
	r.RUnlock()

	// Using the derived shared secret, ensure the integrity of the routing
	// information by checking the attached MAC without leaking timing
	// information.

	message := append(append(routeInfo[:], mixHeader.HopPayload[:]...), assocData...)
	calculatedMac := calcMac(generateKey("mu", sharedSecret), message)
	if !hmac.Equal(headerMac[:], calculatedMac[:]) {
		return nil, fmt.Errorf("MAC mismatch %x != %x, rejecting forwarding message", headerMac, calculatedMac)
	}

	// The MAC checks out, mark this current shared secret as
	// processed in order to mitigate future replay attacks. We
	// need to check to see if we already know the secret again
	// since a replay might have happened while we were checking
	// the MAC.
	r.Lock()
	if _, ok := r.seenSecrets[sharedSecret]; ok {
		r.RUnlock()
		return nil, ErrReplayedPacket
	}
	r.seenSecrets[sharedSecret] = struct{}{}
	r.Unlock()

	// Attach the padding zeroes in order to properly strip an encryption
	// layer off the routing info revealing the routing information for the
	// next hop.
	var hopInfo [numStreamBytes]byte
	streamBytes := generateCipherStream(generateKey("rho", sharedSecret), numStreamBytes)
	headerWithPadding := append(routeInfo[:], bytes.Repeat([]byte{0}, 2*securityParameter)...)
	xor(hopInfo[:], headerWithPadding, streamBytes)

	// Randomize the DH group element for the next hop using the
	// deterministic blinding factor.
	blindingFactor := computeBlindingFactor(dhKey, sharedSecret[:])
	nextDHKey := blindGroupElement(dhKey, blindingFactor[:])

	// Parse out the ID of the next node in the route.
	var nextHop [securityParameter]byte
	copy(nextHop[:], hopInfo[:securityParameter])

	// MAC and MixHeader for the next hop.
	var nextMac [securityParameter]byte
	copy(nextMac[:], hopInfo[securityParameter:securityParameter*2])
	var nextMixHeader [routingInfoSize]byte
	copy(nextMixHeader[:], hopInfo[securityParameter*2:])

	hopPayloadsWithPadding := append(mixHeader.HopPayload[:], bytes.Repeat([]byte{0x00}, HopPayloadSize)...)
	hopStreamBytes := generateCipherStream(generateKey("gamma", sharedSecret), uint(len(hopPayloadsWithPadding)))
	xor(hopPayloadsWithPadding, hopPayloadsWithPadding, hopStreamBytes)

	copy(hopPayload[:], hopPayloadsWithPadding[:HopPayloadSize])
	var nextHopPayloads [NumMaxHops * HopPayloadSize]byte
	copy(nextHopPayloads[:], hopPayloadsWithPadding[HopPayloadSize:])

	nextFwdMsg := &OnionPacket{
		Header: &MixHeader{
			Version:      onionPkt.Header.Version,
			EphemeralKey: nextDHKey,
			RoutingInfo:  nextMixHeader,
			HeaderMAC:    nextMac,
			HopPayload:   nextHopPayloads,
		},
	}

	var action ProcessCode = MoreHops

	if bytes.Compare(bytes.Repeat([]byte{0x00}, 20), nextMac[:]) == 0 {
		action = ExitNode
	}

	return &ProcessedPacket{
		Action:     action,
		NextHop:    nextHop,
		Packet:     nextFwdMsg,
		HopPayload: hopPayload,
	}, nil
}