# smp.tcl --
#
#       This file is a part of the Off-the-Record messaging protocol
#       implementation. It contains the OTR SMP packets serializing and
#       deserializing procedures.
#
# Copyright (c) 2014 Sergei Golovan <sgolovan@nes.ru>
#
# See the file "license.terms" for information on usage and redistribution
# of this file, and for a DISCLAMER OF ALL WARRANTIES.

package require sha256
package require otr::data
package require otr::crypto

package provide otr::smp 0.1

##############################################################################

namespace eval ::otr::smp {
    variable q [expr {($::otr::crypto::Prime-1)/2}]
    variable g1 $::otr::crypto::G
    variable debug 0
}

# ::otr::smp::createSMPMessage1 --
#
#       Return the SMP message 1 or 1Q binary packet.
#
# Arguments:
#       smpstate            SMP state to check.
#       privkey             Your DSA private key.
#       pubkey              Peer's DSA public key.
#       ssid                Secure session ID.
#       usersecret          User part of the secret.
#       -question question  If present, then the 1Q message is created.
#
# Result:
#       Tuple {smpstate, type, payload, x a2 a3} where smpstate is a new SMP
#       state, type and payload make a binary to encrypt and send, x, a2, a3
#       are the values to save for the next SMP spteps if SMP state is
#       SMPSTATE_EXPECT1. Otherwise x, a2, a3 aren't returned.
#
# Side effects:
#       Four 128-bit random numbers are generated.
#       Modulus is taken from a global variable.

proc ::otr::smp::createSMPMessage1 {smpstate privkey pubkey ssid \
                                    usersecret args} {
    variable q
    variable g1

    switch -- $smpstate {
        SMPSTATE_EXPECT1 {
            set a2 [::otr::crypto::random 128]
            set a3 [::otr::crypto::random 128]
            set r2 [::otr::crypto::random 128]
            set r3 [::otr::crypto::random 128]

            set prefix ""
            set type 2
            foreach {key val} $args {
                switch -- $key {
                    -question {
                        set prefix [encoding convertto utf-8 $val]\x00
                        set type 7
                    }
                }
            }

            set x [Secret $privkey $pubkey $ssid $usersecret]
            set g2a [Exp $g1 $a2]
            set g3a [Exp $g1 $a3]
            set c2 [Hash 1 [Exp $g1 $r2]]
            set D2 [expr {($r2-$a2*$c2) % $q}]
            set c3 [Hash 2 [Exp $g1 $r3]]
            set D3 [expr {($r3-$a3*$c3) % $q}]

            return [list SMPSTATE_EXPECT2 $type \
                         $prefix[Payload $g2a $c2 $D2 $g3a $c3 $D3] \
                         $x $a2 $a3]
        }
        default {
            return {SMPSTATE_EXPECT1 6 ""}
        }
    }
}

# ::otr::smp::processSMPMessage1 --
#
#       Process SMP message 1 (or 1Q without the question) and create
#       SMP message 2 to reply.
#
# Arguments:
#       smpstate            SMP state to check.
#       data                Message to parse.
#       privkey             Your DSA private key.
#       pubkey              Peer's DSA public key.
#       ssid                Secure session ID.
#       usersecret          User part of the secret.
#
# Result:
#       Tuple {smpstate, type, payload, g3a, g2, g3, b3, Pb, Qb} where
#       smpstate is a new SMP state, payload is a binary to encrypt and
#       send, the rest are the values to save for the next SMP spteps if SMP
#       state is SMPSTATE_EXPECT1. Otherwise, g3a, g2, g3, b3, Pb, Qb aren't
#       returned.
#
# Side effects:
#       Seven 128-bit random numbers are generated.
#       Modulus is taken from a global variable.

proc ::otr::smp::processSMPMessage1 {smpstate data privkey pubkey ssid
                                     usersecret} {
    variable q
    variable g1

    switch -- $smpstate {
        SMPSTATE_EXPECT1 {
            set b2 [::otr::crypto::random 128]
            set b3 [::otr::crypto::random 128]
            set r2 [::otr::crypto::random 128]
            set r3 [::otr::crypto::random 128]
            set r4 [::otr::crypto::random 128]
            set r5 [::otr::crypto::random 128]
            set r6 [::otr::crypto::random 128]

            if {[catch {ParsePayload $data} res]} {
                Debug 1 "Parse of SMP message 1 failed"
                return [list $smpstate 6 ""]
            }

            lassign $res g2a c2 D2 g3a c3 D3

            if {![Check $g2a] || ![Check $g3a]} {
                Debug 1 "Check 1 of SMP message 1 failed"
                return [list $smpstate 6 ""]
            }

            set myc2 [Hash 1 [Mult [Exp $g1 $D2] [Exp $g2a $c2]]]
            if {$myc2 != $c2} {
                Debug 1 "Check 2 of SMP message 1 failed"
                return [list $smpstate 6 ""]
            }

            set myc3 [Hash 2 [Mult [Exp $g1 $D3] [Exp $g3a $c3]]]
            if {$myc3 != $c3} {
                Debug 1 "Check 3 of SMP message 1 failed"
                return [list $smpstate 6 ""]
            }

            set y [Secret $pubkey $privkey $ssid $usersecret]
            set g2b [Exp $g1 $b2]
            set g3b [Exp $g1 $b3]
            set c2 [Hash 3 [Exp $g1 $r2]]
            set D2 [expr {($r2-$b2*$c2) % $q}]
            set c3 [Hash 4 [Exp $g1 $r3]]
            set D3 [expr {($r3-$b3*$c3) % $q}]
            set g2 [Exp $g2a $b2]
            set g3 [Exp $g3a $b3]
            set Pb [Exp $g3 $r4]
            set Qb [Mult [Exp $g1 $r4] [Exp $g2 $y]]
            set cP [Hash 5 [Exp $g3 $r5] [Mult [Exp $g1 $r5] [Exp $g2 $r6]]]
            set D5 [expr {($r5-$r4*$cP) % $q}]
            set D6 [expr {($r6-$y*$cP) % $q}]

            return \
                [list SMPSTATE_EXPECT3 3 \
                      [Payload $g2b $c2 $D2 $g3b $c3 $D3 $Pb $Qb $cP $D5 $D6] \
                      $g3a $g2 $g3 $b3 $Pb $Qb]
        }
        default {
            # Abort SMP
            return [list SMPSTATE_EXPECT1 6 ""]
        }
    }
}

# ::otr::smp::processSMPMessage2 --
#
#       Process SMP message 2 and create SMP message 3 to reply.
#
# Arguments:
#       smpstate            SMP state to check.
#       data                Message to parse.
#       x a2 a3             SMP values returned by [createSMPMessage1].
#
# Result:
#       Tuple {smpstate, type, payload, Pa, Qa, cP, D5, D6, Ra, cR, D7} where
#       smpstate is a new SMP state, type and payload make a binary to
#       encrypt and send, the rest are the values to save for the next SMP
#       steps if SMP state is SMPSTATE_EXPECT2. Otherwise, Pa, Qa, cP, D5,
#       D6, Ra, cR, D7 aren't returned.
#
# Side effects:
#       Four 128-bit random numbers are generated.
#       Modulus is taken from a global variable.

proc ::otr::smp::processSMPMessage2 {smpstate data x a2 a3} {
    variable q
    variable g1

    switch -- $smpstate {
        SMPSTATE_EXPECT2 {
            set r4 [::otr::crypto::random 128]
            set r5 [::otr::crypto::random 128]
            set r6 [::otr::crypto::random 128]
            set r7 [::otr::crypto::random 128]

            if {[catch {ParsePayload $data} res]} {
                Debug 1 "Parse of SMP message 2 failed"
                return [list $smpstate 6 ""]
            }

            lassign $res g2b c2 D2 g3b c3 D3 Pb Qb cP D5 D6

            if {![Check $g2b] || ![Check $g3b] ||
                    ![Check $Pb] || ![Check $Qb]} {
                Debug 1 "Check 1 of SMP message 2 failed"
                return [list $smpstate 6 ""]
            }

            set myc2 [Hash 3 [Mult [Exp $g1 $D2] [Exp $g2b $c2]]]
            if {$myc2 != $c2} {
                Debug 1 "Check 2 of SMP message 2 failed"
                return [list $smpstate 6 ""]
            }

            set myc3 [Hash 4 [Mult [Exp $g1 $D3] [Exp $g3b $c3]]]
            if {$myc3 != $c3} {
                Debug 1 "Check 3 of SMP message 2 failed"
                return [list $smpstate 6 ""]
            }

            set g2 [Exp $g2b $a2]
            set g3 [Exp $g3b $a3]
            set mycP [Hash 5 [Mult [Exp $g3 $D5] [Exp $Pb $cP]] \
                             [Mult [Exp $g1 $D5] \
                                   [Mult [Exp $g2 $D6] [Exp $Qb $cP]]]]
            if {$mycP != $cP} {
                Debug 1 "Check 4 of SMP message 2 failed"
                return [list $smpstate 6 ""]
            }

            set Pa [Exp $g3 $r4]
            set Qa [Mult [Exp $g1 $r4] [Exp $g2 $x]]
            set cP [Hash 6 [Exp $g3 $r5] [Mult [Exp $g1 $r5] [Exp $g2 $r6]]]
            set D5 [expr {($r5-$r4*$cP) % $q}]
            set D6 [expr {($r6-$x*$cP) % $q}]
            set PaPb [Mult $Pa [Inv $Pb]]
            set QaQb [Mult $Qa [Inv $Qb]]
            set Ra [Exp $QaQb $a3]
            set cR [Hash 7 [Exp $g1 $r7] [Exp $QaQb $r7]]
            set D7 [expr {($r7-$a3*$cR) % $q}]

            return [list SMPSTATE_EXPECT4 4 \
                         [Payload $Pa $Qa $cP $D5 $D6 $Ra $cR $D7] \
                         $g3b $PaPb $QaQb $Ra]
        }
        default {
            # Abort SMP
            return [list SMPSTATE_EXPECT1 6 ""]
        }
    }
}

# ::otr::smp::processSMPMessage3 --
#
#       Process SMP message 3 and create SMP message 4 to reply.
#
# Arguments:
#       smpstate            SMP state to check.
#       data                Message to parse.
#       g3a g2 g3 b3 Pb Qb  SMP values returned by [processSMPMessage1].
#
# Result:
#       Tuple {smpstate, type, payload, result} where smpstate is a new SMP
#       state, type and payload make a binary to encrypt and send, the result
#       equals 1 if authentication succeeded, or 0 if not, if SMP state is
#       SMPSTATE_EXPECT3. Otherwise, no result field is returned.
#
# Side effects:
#       One 128-bit random numbers are generated.
#       Modulus is taken from a global variable.

proc ::otr::smp::processSMPMessage3 {smpstate data g3a g2 g3 b3 Pb Qb} {
    variable q
    variable g1

    switch -- $smpstate {
        SMPSTATE_EXPECT3 {
            set r7 [::otr::crypto::random 128]

            if {[catch {ParsePayload $data} res]} {
                Debug 1 "Parse of SMP message 3 failed"
                return [list $smpstate 6 ""]
            }

            lassign $res Pa Qa cP D5 D6 Ra cR D7

            set QaQb [Mult $Qa [Inv $Qb]]

            if {![Check $Pa] || ![Check $Qa] || ![Check $Ra]} {
                Debug 1 "Check 1 of SMP message 3 failed"
                return [list $smpstate 6 ""]
            }

            set mycP [Hash 6 [Mult [Exp $g3 $D5] [Exp $Pa $cP]] \
                             [Mult [Exp $g1 $D5] \
                                   [Mult [Exp $g2 $D6] [Exp $Qa $cP]]]]
            if {$mycP != $cP} {
                Debug 1 "Check 2 of SMP message 3 failed"
                return [list $smpstate 6 ""]
            }

            set mycR [Hash 7 [Mult [Exp $g1 $D7] [Exp $g3a $cR]] \
                             [Mult [Exp $QaQb $D7] [Exp $Ra $cR]]]
            if {$mycR != $cR} {
                Debug 1 "Check 3 of SMP message 3 failed"
                return [list $smpstate 6 ""]
            }

            set Rb [Exp $QaQb $b3]
            set cR [Hash 8 [Exp $g1 $r7] [Exp $QaQb $r7]]
            set D7 [expr {($r7-$b3*$cR) % $q}]

            set PaPb [Mult $Pa [Inv $Pb]]
            set Rab [Exp $Ra $b3]
            set res [expr {$PaPb == $Rab}]

            Debug 1 "$smpstate $res"

            return [list SMPSTATE_EXPECT1 5 \
                         [Payload $Rb $cR $D7] \
                         $res]
        }
        default {
            # Abort SMP
            return [list SMPSTATE_EXPECT1 6 ""]
        }
    }
}

# ::otr::smp::processSMPMessage4 --
#
#       Process SMP message 4 and return the authentication result.
#
# Arguments:
#       smpstate            SMP state to check.
#       data                Message to parse.
#       a3 g3b PaPb QaQb Ra SMP values returned by [createMessage1] and
#                           [processSMPMessage2].
#
# Result:
#       Tuple {smpstate, type, payload, result} where smpstate is a new SMP
#       state, type and payload make a binary to encrypt and send, the result
#       equals 1 if authentication succeeded, or 0 if not, if SMP state is
#       SMPSTATE_EXPECT3. Otherwise, no result field is returned.
#
# Side effects:
#       Modulus is taken from a global variable.

proc ::otr::smp::processSMPMessage4 {smpstate data a3 g3b PaPb QaQb Ra} {
    variable q
    variable g1

    switch -- $smpstate {
        SMPSTATE_EXPECT4 {
            if {[catch {ParsePayload $data} res]} {
                Debug 1 "Parse of SMP message 4 failed"
                return [list $smpstate 6 ""]
            }

            lassign $res Rb cR D7

            if {![Check $Rb]} {
                Debug 1 "Check 1 of SMP message 4 failed"
                return [list $smpstate 6 ""]
            }

            set mycR [Hash 8 [Mult [Exp $g1 $D7] [Exp $g3b $cR]] \
                             [Mult [Exp $QaQb $D7] [Exp $Rb $cR]]]
            if {$mycR != $cR} {
                Debug 1 "Check 2 of SMP message 3 failed"
                return [list $smpstate 6 ""]
            }

            set Rab [Exp $Rb $a3]
            set res [expr {$PaPb == $Rab}]

            Debug 1 "$smpstate $res"

            return [list SMPSTATE_EXPECT1 0 "" $res]
        }
        default {
            # Abort SMP
            return [list SMPSTATE_EXPECT1 6 ""]
        }
    }
}

# ::otr::smp::Check --
#
#       Wrapper to ::otr::crypto::DHCheck to make the call shorter.
#
# Arguments:
#       a               Number to check.
#
# Result:
#       1 if $a is greater than 1 and less than P-1 (where P is a
#       D-H prime).
#
# Side effects:
#       Modulus is taken from a global variable.

proc ::otr::smp::Check {a} {
    ::otr::crypto::DHCheck $a
}

# ::otr::smp::Exp --
#
#       Exponent a**b mod P where P is a D-H prime.
#
# Arguments:
#       a           Exponent base.
#       b           Exponent index.
#
# Result:
#       a**b mod P.
#
# Side effects:
#       Modulus is taken from a global variable.

proc ::otr::smp::Exp {a b} {
    ::otr::crypto::DHSecret $a $b
}

# ::otr::smp::Mult --
#
#       Multiplication modulo P.
#
# Arguments:
#       a           First multiplier.
#       b           Second multiplier.
#
# Result:
#       a*b mod P.
#
# Side effects:
#       Modulus is taken from a global variable.

proc ::otr::smp::Mult {a b} {
    ::otr::crypto::Mult $::otr::crypto::Prime $a $b
}

# ::otr::smp::Inv --
#
#       Inverse modulo P.
#
# Arguments:
#       a           Number.
#
# Result:
#       a**(-1) mod P, or b such that a*b = 1 mod P.
#
# Side effects:
#       Modulus is taken from a global variable.

proc ::otr::smp::Inv {a} {
    ::otr::crypto::Inverse $::otr::crypto::Prime $a
}

# ::otr::smp::ParsePayload --
#
#       Decode SMP payload - a series of long integers.
#
# Arguments:
#       data        Binary to parse.
#
# Result:
#       List of integers.
#
# Side effects:
#       Error is raised if parsing fails.

proc ::otr::smp::ParsePayload {data} {
    set res {}
    lassign [::otr::data::decode INT $data] count data
    for {set i 0} {$i < $count} {incr i} {
        lassign [::otr::data::decode MPI $data] num data
        lappend res $num
    }
    set res
}

# ::otr::smp::Payload --
#
#       Create SMP payload from a series of integers.
#
# Arguments:
#       args        List of integers.
#
# Result:
#       SMP binary.
#
# Side effects:
#       None.

proc ::otr::smp::Payload {args} {
    set res ""
    append res [::otr::data::encode INT [llength $args]]
    foreach num $args {
        append res [::otr::data::encode MPI $num]
    }
    set res
}

# ::otr::smp::Hash --
#
#       SMP hash for its zero knowledge proofs.
#
# Arguments:
#       version     Number.
#       arg1        The first number to hash.
#       arg2        (optional) The second number to hash.
#
# Result:
#       Integer representation of the SHA256 hash of the concatenated given
#       arguments.
#
# Side effects:
#       None.

proc ::otr::smp::Hash {version arg1 {arg2 ""}} {
    set res ""
    append res [::otr::data::encode BYTE $version]
    append res [::otr::data::encode MPI $arg1]
    if {$arg2 ne ""} {
        append res [::otr::data::encode MPI $arg2]
    }
    ::otr::crypto::Bits2Int [::sha2::sha256 -bin $res]
}

# ::otr::smp::Secret --
#
#       Return the common secret to check for equality using the SMP.
#
# Arguments:
#       pubkey1     The first public key.
#       pubkey2     The second public key.
#       ssid        The secure session ID.
#       secret      The users' common secret.
#
# Result:
#       Secret to compare.
#
# Side effects:
#       None.

proc ::otr::smp::Secret {pubkey1 pubkey2 ssid secret} {
    set res ""
    append res [::otr::data::encode BYTE 1]
    append res [::otr::crypto::DSAFingerprint $pubkey1]
    append res [::otr::crypto::DSAFingerprint $pubkey2]
    append res $ssid
    append res [encoding convertto utf-8 $secret]
    ::otr::crypto::Bits2Int [::sha2::sha256 -bin $res]
}

# ::otr::smp::Debug --
#
#       Prints debug information.
#
# Arguments:
#       level   Debug level.
#       str     Debug message.
#
# Result:
#       An empty string.
#
# Side effects:
#       A debug message is printed to the console if the value of
#       ::otr::smp::debug variable is not less than num.

proc ::otr::smp::Debug {level str} {
    variable debug

    if {$debug >= $level} {
        puts "[clock format [clock seconds] -format %T]\
              [lindex [info level -1] 0] $str"
    }

    return
}

# vim:ts=8:sw=4:sts=4:et
