44 "bytes"
55 "crypto/hmac"
66 "crypto/sha256"
7- "errors"
87 "fmt"
98
109 "github.com/aead/chacha20"
@@ -249,10 +248,11 @@ const onionErrorLength = 2 + 2 + 256 + sha256.Size
249248func (o * OnionErrorDecrypter ) DecryptError (encryptedData []byte ) (
250249 * DecryptedError , error ) {
251250
252- // Ensure the error message length is as expected.
253- if len (encryptedData ) != onionErrorLength {
251+ // Ensure the error message length is enough to contain the payloads and
252+ // hmacs blocks.
253+ if len (encryptedData ) < hmacsAndPayloadsLen {
254254 return nil , fmt .Errorf ("invalid error length: " +
255- "expected %v got %v" , onionErrorLength ,
255+ "expected at least %v got %v" , hmacsAndPayloadsLen ,
256256 len (encryptedData ))
257257 }
258258
@@ -292,30 +292,40 @@ func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte) (
292292 // encryption from the encrypted error payload.
293293 encryptedData = onionEncrypt (& sharedSecret , encryptedData )
294294
295- // Next, we'll need to separate the data, from the MAC itself
296- // so we can reconstruct and verify it.
297- expectedMac := encryptedData [:sha256 .Size ]
298- data := encryptedData [sha256 .Size :]
299-
300- // With the data split, we'll now re-generate the MAC using its
301- // specified key.
302- umKey := generateKey ("um" , & sharedSecret )
303- h := hmac .New (sha256 .New , umKey [:])
304- h .Write (data )
305-
306- // If the MAC matches up, then we've found the sender of the
307- // error and have also obtained the fully decrypted message.
308- realMac := h .Sum (nil )
309- if hmac .Equal (realMac , expectedMac ) && sender == 0 {
295+ message , payloads , hmacs := getMsgComponents (encryptedData )
296+
297+ final := payloads [0 ] == payloadFinal
298+ // TODO: Extract hold time from payload.
299+
300+ expectedHmac := calculateHmac (sharedSecret , i , message , payloads , hmacs )
301+ actualHmac := hmacs [i * sha256 .Size : (i + 1 )* sha256 .Size ]
302+
303+ // If the hmac does not match up, exit with a nil message.
304+ if ! bytes .Equal (actualHmac , expectedHmac [:]) && sender == 0 {
310305 sender = i + 1
311- msg = data
306+ msg = nil
312307 }
308+
309+ // If we are at the node that is the source of the error, we can now
310+ // save the message in our return variable.
311+ if final && sender == 0 {
312+ sender = i + 1
313+ msg = message
314+ }
315+
316+ // Shift payloads and hmacs to the left to prepare for the next
317+ // iteration.
318+ shiftPayloadsLeft (payloads )
319+ shiftHmacsLeft (hmacs )
313320 }
314321
315- // If the sender index is still zero, then we haven't found the sender,
316- // meaning we've failed to decrypt.
322+ // If the sender index is still zero, all hmacs checked out but none of the
323+ // payloads was a final payload. In this case we must be dealing with a max
324+ // length route and a final hop that returned an intermediate payload. Blame
325+ // the final hop.
317326 if sender == 0 {
318- return nil , errors .New ("unable to retrieve onion failure" )
327+ sender = NumMaxHops
328+ msg = nil
319329 }
320330
321331 return & DecryptedError {
@@ -325,6 +335,132 @@ func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte) (
325335 }, nil
326336}
327337
338+ const (
339+ totalHmacs = (NumMaxHops * (NumMaxHops + 1 )) / 2
340+ allHmacsLen = totalHmacs * sha256 .Size
341+ hmacsAndPayloadsLen = allHmacsLen + allPayloadsLen
342+
343+ // payloadLen is the size of the per-node payload. It consists of a 1-byte
344+ // payload type and an 8-byte hold time.
345+ payloadLen = 1 + 8
346+
347+ allPayloadsLen = payloadLen * NumMaxHops
348+
349+ payloadFinal = 1
350+ payloadIntermediate = 0
351+ )
352+
353+ func shiftHmacsRight (hmacs []byte ) {
354+ if len (hmacs ) != allHmacsLen {
355+ panic ("invalid hmac block length" )
356+ }
357+
358+ srcIdx := totalHmacs - 2
359+ destIdx := totalHmacs - 1
360+ copyLen := 1
361+ for i := 0 ; i < NumMaxHops - 1 ; i ++ {
362+ copy (hmacs [destIdx * sha256 .Size :], hmacs [srcIdx * sha256 .Size :(srcIdx + copyLen )* sha256 .Size ])
363+
364+ copyLen ++
365+
366+ srcIdx -= copyLen + 1
367+ destIdx -= copyLen
368+ }
369+ }
370+
371+ func shiftHmacsLeft (hmacs []byte ) {
372+ if len (hmacs ) != allHmacsLen {
373+ panic ("invalid hmac block length" )
374+ }
375+
376+ srcIdx := NumMaxHops
377+ destIdx := 1
378+ copyLen := NumMaxHops - 1
379+ for i := 0 ; i < NumMaxHops - 1 ; i ++ {
380+ copy (hmacs [destIdx * sha256 .Size :], hmacs [srcIdx * sha256 .Size :(srcIdx + copyLen )* sha256 .Size ])
381+
382+ srcIdx += copyLen
383+ destIdx += copyLen + 1
384+ copyLen --
385+ }
386+ }
387+
388+ func shiftPayloadsRight (payloads []byte ) {
389+ if len (payloads ) != allPayloadsLen {
390+ panic ("invalid payload block length" )
391+ }
392+
393+ copy (payloads [payloadLen :], payloads )
394+ }
395+
396+ func shiftPayloadsLeft (payloads []byte ) {
397+ if len (payloads ) != allPayloadsLen {
398+ panic ("invalid payload block length" )
399+ }
400+
401+ copy (payloads , payloads [payloadLen :NumMaxHops * payloadLen ])
402+ }
403+
404+ // getMsgComponents splits a complete failure message into its components
405+ // without re-allocating memory.
406+ func getMsgComponents (data []byte ) ([]byte , []byte , []byte ) {
407+ payloads := data [len (data )- hmacsAndPayloadsLen : len (data )- allHmacsLen ]
408+ hmacs := data [len (data )- allHmacsLen :]
409+ message := data [:len (data )- hmacsAndPayloadsLen ]
410+
411+ return message , payloads , hmacs
412+ }
413+
414+ // calculateHmac calculates an hmac given a shared secret and a presumed
415+ // position in the path. Position is expressed as the distance to the error
416+ // source. The error source itself is at position 0.
417+ func calculateHmac (sharedSecret Hash256 , position int ,
418+ message , payloads , hmacs []byte ) []byte {
419+
420+ var dataToHmac []byte
421+
422+ // Include payloads including our own.
423+ dataToHmac = append (dataToHmac , payloads [:(NumMaxHops - position )* payloadLen ]... )
424+
425+ // Include downstream hmacs.
426+ var downstreamHmacsIdx = position + NumMaxHops
427+ for j := 0 ; j < NumMaxHops - position - 1 ; j ++ {
428+ dataToHmac = append (dataToHmac , hmacs [downstreamHmacsIdx * sha256 .Size :(downstreamHmacsIdx + 1 )* sha256 .Size ]... )
429+
430+ downstreamHmacsIdx += NumMaxHops - j - 1
431+ }
432+
433+ // Include message.
434+ dataToHmac = append (dataToHmac , message ... )
435+
436+ // Calculate and return hmac.
437+ umKey := generateKey ("um" , & sharedSecret )
438+ hash := hmac .New (sha256 .New , umKey [:])
439+ hash .Write (dataToHmac )
440+
441+ return hash .Sum (nil )
442+ }
443+
444+ // calculateHmac calculates an hmac using the shared secret for this
445+ // OnionErrorEncryptor instance.
446+ func (o * OnionErrorEncrypter ) calculateHmac (position int ,
447+ message , payloads , hmacs []byte ) []byte {
448+
449+ return calculateHmac (o .sharedSecret , position , message , payloads , hmacs )
450+ }
451+
452+ // addHmacs updates the failure data with a series of hmacs corresponding to all
453+ // possible positions in the path for the current node.
454+ func (o * OnionErrorEncrypter ) addHmacs (data []byte ) {
455+ message , payloads , hmacs := getMsgComponents (data )
456+
457+ for i := 0 ; i < NumMaxHops ; i ++ {
458+ hmac := o .calculateHmac (i , message , payloads , hmacs )
459+
460+ copy (hmacs [i * sha256 .Size :], hmac )
461+ }
462+ }
463+
328464// EncryptError is used to make data obfuscation using the generated shared
329465// secret.
330466//
@@ -338,12 +474,40 @@ func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte) (
338474// failure and its origin.
339475func (o * OnionErrorEncrypter ) EncryptError (initial bool , data []byte ) []byte {
340476 if initial {
341- umKey := generateKey ("um" , & o .sharedSecret )
342- hash := hmac .New (sha256 .New , umKey [:])
343- hash .Write (data )
344- h := hash .Sum (nil )
345- data = append (h , data ... )
477+ data = o .initializePayload (data )
478+ } else {
479+ o .addIntermediatePayload (data )
346480 }
347481
482+ // Update hmac block.
483+ o .addHmacs (data )
484+
485+ // Obfuscate.
348486 return onionEncrypt (& o .sharedSecret , data )
349487}
488+
489+ func (o * OnionErrorEncrypter ) initializePayload (message []byte ) []byte {
490+ // Add space for payloads and hmacs.
491+ data := make ([]byte , len (message )+ hmacsAndPayloadsLen )
492+ copy (data , message )
493+
494+ _ , payloads , _ := getMsgComponents (data )
495+
496+ // Signal final hops in the payload.
497+ // TODO: Add hold time to payload.
498+ payloads [0 ] = payloadFinal
499+
500+ return data
501+ }
502+
503+ func (o * OnionErrorEncrypter ) addIntermediatePayload (data []byte ) {
504+ _ , payloads , hmacs := getMsgComponents (data )
505+
506+ // Shift hmacs and payloads to create space for the payload.
507+ shiftPayloadsRight (payloads )
508+ shiftHmacsRight (hmacs )
509+
510+ // Signal intermediate hop in the payload.
511+ // TODO: Add hold time to payload.
512+ payloads [0 ] = payloadIntermediate
513+ }
0 commit comments