# auth.tcl --
#
#       This file is a part of the Off-the-Record messaging protocol
#       implementation. It contains the OTR AKE 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::auth 0.1

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

namespace eval ::otr::auth {}

# ::otr::auth::createDHCommitMessage --
#
#       Assemble the first OTR AKE message which Bob sends to Alice - the D-H
#       commit one.
#
# Arguments:
#       version         Protocol version (2 or 3).
#       r               Random binary to use as an AES key.
#       x               D-H private key.
#       -sinstance num  (only for version 3) The sender instance tag.
#       -rinstance num  (only for version 3) The receiver instance tag.
#
# Result:
#       Tuple {authstate msgstate message} where authstate and msgstate are
#       the new FSM state, and message is a BASE64 encoded OTR packet with the
#       message inside.
#
# Side effects:
#       None.

proc ::otr::auth::createDHCommitMessage {version authstate msgstate r x args} {
    set sinstance 0x100
    set rinstance 0
    foreach {key val} $args {
        switch -- $key {
            -sinstance { set sinstance $val }
            -rinstance { set rinstance $val }
        }
    }
    set message [createDHCommitPayload $version \
                                       $r \
                                       $x \
                                       $sinstance \
                                       $rinstance]
    set authstate AUTHSTATE_AWAITING_DHKEY
    list $authstate $msgstate $message
}

# ::otr::auth::createDHCommitPayload --
#
#       Assemble the first OTR AKE message which Bob sends to Alice - the D-H
#       commit one.
#
# Arguments:
#       version         Protocol version (2 or 3).
#       r               Random binary to use as an AES key.
#       x               D-H private key.
#       sinstance       (only for version 3) The sender instance tag.
#       rinstance       (only for version 3) The receiver instance tag.
#
# Result:
#       BASE64 encoded binary OTR packet with the message inside.
#
# Side effects:
#       None.

proc ::otr::auth::createDHCommitPayload {version r x sinstance rinstance} {
    set res ""
    append res [::otr::data::encode SHORT $version]
    append res [::otr::data::encode BYTE 0x02]
    if {$version > 2} {
        append res [::otr::data::encode INT $sinstance]
        append res [::otr::data::encode INT $rinstance]
    }
    set gx [::otr::crypto::DHGx $x]
    set gxmpi [::otr::data::encode MPI $gx]
    set egxmpi [::otr::crypto::aes $gxmpi $r 0]
    append res [::otr::data::encode DATA $egxmpi]
    append res [::otr::data::encode DATA [::sha2::sha256 -bin $gxmpi]]
    ::otr::data::encodeMessage $res
}

# ::otr::auth::processDHCommitMessage --
#
#       Pick the received D-H Commit message without the protocol version,
#       packet type and instance tags, as well as the D-H private key, and
#       create a suitable reply message.
#
# Arguments:
#       version         OTR protocol version.
#       authstate       AKE authentication state.
#       msgstate        Message state.
#       data            D-H commit packet to process.
#       x               D-H private key which is to be used for creating the
#                       answer.
#       -sinstance num  (only for version 3) The sender instance tag.
#       -rinstance num  (only for version 3) The receiver instance tag.
#       -r r            One-time AES key to use only in auth state
#                       AUTHSTATE_AWAITING_DHKEY to recreate D-H commit
#                       message.
#
# Result:
#       Tuple {auhstate msgstate message} or {authstate msgstate message
#       egxmpi hgxmpi} with new FSM state, the message to reply, and encrypted
#       and hashed peer's D-H public key to store if the current FSM state is
#       appropriate.
#
# Side effects:
#       None.

proc ::otr::auth::processDHCommitMessage {version authstate msgstate data \
                                          x args} {
    set sinstance 0x100
    set rinstance 0x100
    foreach {key val} $args {
        switch -- $key {
            -sinstance { set sinstance $val }
            -rinstance { set rinstance $val }
            -r { set r $val }
        }
    }

    if {[catch {parseDHCommitMessage $version $data} res]} {
        return [list $authstate $msgstate ""]
    }

    lassign $res egxmpi hgxmpi

    switch -- $authstate {
        AUTHSTATE_NONE -
        AUTHSTATE_AWAITING_SIG {
            set authstate AUTHSTATE_AWAITING_REVEALSIG
            return [list $authstate $msgstate [createDHKeyMessage \
                                                    $version \
                                                    $x \
                                                    -sinstance $rinstance \
                                                    -rinstance $sinstance] \
                                              $egxmpi $hgxmpi]
        }
        AUTHSTATE_AWAITING_DHKEY {
            set gx [::otr::crypto::DHGx $x]
            set gxmpi [::otr::data::encode MPI $gx]
            set myhgxmpi [::sha2::sha256 -bin $gxmpi]

            if {[::otr::data::Bin2Int $myhgxmpi] >
                                        [::otr::data::Bin2Int $hgxmpi]} {
                return [createDHCommitMessage $version \
                                              $authstate \
                                              $msgstate \
                                              $r \
                                              $x \
                                              -sinstance $rinstance]
            } else {
                set authstate AUTHSTATE_AWAITING_REVEALSIG
                return [list $authstate $msgstate [createDHKeyMessage \
                                                      $version \
                                                      $x \
                                                      -sinstance $rinstance \
                                                      -rinstance $sinstance] \
                                                  $egxmpi $hgxmpi]
            }
        }
        AUTHSTATE_AWAITING_REVEALSIG {
            return [list $authstate $msgstate [createDHKeyMessage \
                                                    $version \
                                                    $x \
                                                    -sinstance $rinstance \
                                                    -rinstance $sinstance] \
                                              $egxmpi $hgxmpi]
        }
    }
}

# ::otr::auth::parseDHCommitMessage --
#
#       Parse the D-H commit message (without version and packet type which
#       were extracted earlier).
#
# Arguments:
#       version         Protocol version (2 or 3).
#       data            Payload to parse.
#
# Result:
#       Tuple {egxmpi, H(gxmpi)}.
#
# Side effects:
#       Error is raised if decoding is failed for some reason.

proc ::otr::auth::parseDHCommitMessage {version data} {
    lassign [::otr::data::decode DATA $data] egxmpi data
    lassign [::otr::data::decode DATA $data] hgxmpi data
    list $egxmpi $hgxmpi
}

# ::otr::auth::createDHKeyMessage --
#
#       Assemble the first OTR AKE message which Alice returns to Bob - the
#       D-H key one.
#
# Arguments:
#       version         Protocol version (2 or 3).
#       y               D-H private key.
#       -sinstance num  (only for version 3) The sender instance tag.
#       -rinstance num  (only for version 3) The receiver instance tag.
#
# Result:
#       BASE64 encoded OTR packet with the message inside.
#
# Side effects:
#       None.

proc ::otr::auth::createDHKeyMessage {version y args} {
    set sinstance 0x100
    set rinstance 0
    foreach {key val} $args {
        switch -- $key {
            -sinstance { set sinstance $val }
            -rinstance { set rinstance $val }
        }
    }
    set res ""
    append res [::otr::data::encode SHORT $version]
    append res [::otr::data::encode BYTE 0x0a]
    if {$version > 2} {
        append res [::otr::data::encode INT $sinstance]
        append res [::otr::data::encode INT $rinstance]
    }
    set gy [::otr::crypto::DHGx $y]
    append res [::otr::data::encode MPI $gy]
    ::otr::data::encodeMessage $res
}

# ::otp::auth::processDHKeyMessage --
#
#       Pick the received D-H Key message without the protocol version,
#       packet type and instance tags, as well as the one-time AES key, D-H
#       private key with its keyid, and create a suitable reply message.
#
# Arguments:
#       version         OTR protocol version.
#       authstate       AKE authentication state.
#       msgstate        Message state.
#       data            D-H key packet to process.
#       r               One time AES key to reveal to the peer.
#       x               D-H private key which is to be used for creating the
#                       answer.
#       keyid           Serial number of the above D-H key.
#       -sinstance num  (only for version 3) The sender instance tag.
#       -rinstance num  (only for version 3) The receiver instance tag.
#       -gy gy          The already obtained peer's D-H public key for the case
#                       when it's a repeated D-H key message (used only in
#                       auth state AUTHSTATE_AWAITING_SIG).
#
# Result:
#       Tuple {auhstate msgstate message} or {authstate msgstate message gy}
#       with new FSM state, the message to reply, and the peer's D-H public
#       key  to store if the current FSM state is appropriate.
#
# Side effects:
#       None.

proc ::otr::auth::processDHKeyMessage {version authstate msgstate data
                                       privkey r x keyid args} {
    set sinstance 0x100
    set rinstance 0x100
    foreach {key val} $args {
        switch -- $key {
            -sinstance { set sinstance $val }
            -rinstance { set rinstance $val }
            -gy { set oldgy $val }
        }
    }

    if {[catch {parseDHKeyMessage $version $data} res]} {
        return [list $authstate $msgstate ""]
    }

    lassign $res gy
    if {![::otr::crypto::DHCheck $gy]} {
        return [list $authstate $msgstate ""]
    }

    switch -- $authstate {
        AUTHSTATE_NONE -
        AUTHSTATE_AWAITING_REVEALSIG {
            return [list $authstate $msgstate ""]
        }
        AUTHSTATE_AWAITING_DHKEY {
            lassign [::otr::crypto::AKEKeys $gy $x] ssid c cp m1 m2 m1p m2p

            set authstate AUTHSTATE_AWAITING_SIG
            return [list $authstate $msgstate [createRevealSignatureMessage \
                                                    $version $r $c $m1 $m2 \
                                                    $x $gy $privkey $keyid \
                                                    -sinstance $rinstance \
                                                    -rinstance $sinstance] \
                                              $gy]
        }
        AUTHSTATE_AWAITING_SIG {
            if {$gy != $oldgy} {
                return [list $authstate $msgstate ""]
            } else {
                lassign [::otr::crypto::AKEKeys $gy $x] ssid c cp m1 m2 m1p m2p

                return [list $authstate $msgstate \
                             [createRevealSignatureMessage \
                                        $version $r $c $m1 $m2 \
                                        $x $gy $privkey $keyid \
                                        -sinstance $rinstance \
                                        -rinstance $sinstance] \
                             $gy]
            }
        }
    }
}

# ::otr::auth::parseDHKeyMessage --
#
#       Parse the D-H key message (without version and packet type which
#       were extracted earlier).
#
# Arguments:
#       version         The protocol version (2 or 3).
#       data            The payload to parse.
#
# Result:
#       Tuple {gy}.
#
# Side effects:
#       Error is raised if decoding is failed for some reason.

proc ::otr::auth::parseDHKeyMessage {version data} {
    lassign [::otr::data::decode MPI $data] gy data
    list $gy
}

# ::otr::auth::createRevealSignatureMessage --
#
#       Assemble the third OTR AKE message which Bob sends to Alice - the
#       reveal signature one.
#
# Arguments:
#       version         The protocol version (2 or 3).
#       r               The random value which was used as an AES key.
#       c               AES key computed from the shared secret.
#       m1              MAC key computed from the shared secret.
#       m2              MAC key computed from the shared secret.
#       x               The random value which is the part of shared secret.
#       gy              The part of D-H shared secret.
#       keyB            Long-term private key.
#       keyidB          Serial number for the D-H key.
#       -sinstance num  (only for version 3) The sender instance tag.
#       -rinstance num  (only for version 3) The receiver instance tag.
#
# Result:
#       BASE64 encoded OTR packet with the message inside.
#
# Side effects:
#       None.

proc ::otr::auth::createRevealSignatureMessage {version r c m1 m2 x \
                                                gy keyB keyidB args} {
    set sinstance 0x100
    set rinstance 0
    foreach {key val} $args {
        switch -- $key {
            -sinstance { set sinstance $val }
            -rinstance { set rinstance $val }
        }
    }
    set res ""
    append res [::otr::data::encode SHORT $version]
    append res [::otr::data::encode BYTE 0x11]
    if {$version > 2} {
        append res [::otr::data::encode INT $sinstance]
        append res [::otr::data::encode INT $rinstance]
    }
    append res [::otr::data::encode DATA $r]

    set gx [::otr::crypto::DHGx $x]
    set gxmpi [::otr::data::encode MPI $gx]
    set gympi [::otr::data::encode MPI $gy]
    set pubB [::otr::data::encode PUBKEY $keyB]
    set ikeyidB [::otr::data::encode INT $keyidB]
    set MB [::sha2::hmac -bin -key $m1 $gxmpi$gympi$pubB$ikeyidB]

    set sigBMB [::otr::data::encode SIG [::otr::crypto::sign $MB $keyB]]
    set encr [::otr::crypto::aes $pubB$ikeyidB$sigBMB $c 0]
    set encrfield [::otr::data::encode DATA $encr]
    append res $encrfield
    append res \
           [::otr::data::encode MAC [::sha2::hmac -bin -key $m2 $encrfield]]
    ::otr::data::encodeMessage $res
}

# ::otr::auth::processRevealSignatureMessage --
#
#       Pick the received Reveal signature message without the protocol
#       version, packet type and instance tags, as well as the got earlier
#       peer's encrypted and hashed D-H public key, D-H private key with its
#       keyid, and create a suitable reply message.
#
# Arguments:
#       version         OTR protocol version.
#       authstate       AKE authentication state.
#       msgstate        Message state.
#       data            Reveal signature packet to process.
#       egxmpi          Encrypted peer's D-H public key.
#       hgxmpi          Hashed peer's D-H public key.
#       keyA            DSA private key.
#       y               D-H private key which is to be used for decrypting,
#                       verifying the hash and creating the answer.
#       keyidA          Serial number of the above D-H key.
#       -sinstance num  (only for version 3) The sender instance tag.
#       -rinstance num  (only for version 3) The receiver instance tag.
#
# Result:
#       Tuple {auhstate msgstate message} or {authstate msgstate message key
#       gx keyid} with new FSM state, the message to reply, and the peer's
#       DSA public key, D-H public key and its serial to store if the current
#       FSM state is appropriate.
#
# Side effects:
#       None.

proc ::otr::auth::processRevealSignatureMessage {version authstate msgstate \
                                                 data egxmpi hgxmpi keyA y \
                                                 keyidA args} {
    set sinstance 0x100
    set rinstance 0x100
    foreach {key val} $args {
        switch -- $key {
            -sinstance { set sinstance $val }
            -rinstance { set rinstance $val }
        }
    }

    if {[catch {parseRevealSignatureMessage $version $data} res]} {
        #puts "Reveal signature message is corrupt"
        return [list $authstate $msgstate ""]
    }

    lassign $res r encr hmac

    switch -- $authstate {
        AUTHSTATE_NONE -
        AUTHSTATE_AWAITING_DHKEY -
        AUTHSTATE_AWAITING_SIG {
            #puts "Wrong state for reveal signature message"
            return [list $authstate $msgstate ""]
        }
        AUTHSTATE_AWAITING_REVEALSIG {
            set gxmpi [::otr::crypto::aes $egxmpi $r 0]
            set vhgxmpi [::sha2::sha256 -bin $gxmpi]
            if {$hgxmpi ne $vhgxmpi} {
                #puts "H(Gx) and decrypted Gx from D-H commit differ"
                return [list $authstate $msgstate ""]
            }

            if {[catch {::otr::data::decode MPI $gxmpi} res]} {
                #puts "D-H public key can't be deciphered"
                return [list $authstate $msgstate ""]
            }
            lassign $res gx
            if {![::otr::crypto::DHCheck $gx]} {
                #puts "D-H public key is incorrect"
                return [list $authstate $msgstate ""]
            }
            lassign [::otr::crypto::AKEKeys $gx $y] ssid c cp m1 m2 m1p m2p

            set myhmac \
                [::sha2::hmac -bin -key $m2 [::otr::data::encode DATA $encr]]
            if {[string range $myhmac 0 19] ne $hmac} {
                #puts "HMAC in Reveal signature message is invalid"
                return [list $authstate $msgstate ""]
            }

            set decr [::otr::crypto::aes $encr $c 0]
            if {[catch {parsePubkey $decr} res]} {
                #puts "DSA public key can't be deciphered"
                return [list $authstate $msgstate ""]
            }
            lassign $res keyB keyidB sigBMB

            set gxmpi [::otr::data::encode MPI $gx]
            set gympi [::otr::data::encode MPI [::otr::crypto::DHGx $y]]
            set pubB [::otr::data::encode PUBKEY $keyB]
            set ikeyidB [::otr::data::encode INT $keyidB]
            set MB [::sha2::hmac -bin -key $m1 $gxmpi$gympi$pubB$ikeyidB]

            if {![::otr::crypto::verify $MB $sigBMB $keyB]} {
                #puts "DSA signature in Reveal signature message is invalid"
                return [list $authstate $msgstate ""]
            }

            set authstate AUTHSTATE_NONE
            set msgstate MSGSTATE_ENCRYPTED

            set message [createSignatureMessage $version $cp $m1p $m2p \
                                                $y $gx $keyA $keyidA \
                                                -sinstance $rinstance \
                                                -rinstance $sinstance]
            return [list $authstate $msgstate $message $keyB $gx $keyidB]
        }
    }
}

# ::otr::auth::parseRevealSignatureMessage --
#
#       Parse the reveal signature message (without version and packet type
#       which were extracted earlier).
#
# Arguments:
#       version         The protocol version (2 or 3).
#       data            The payload to parse.
#
# Result:
#       Tuple {r, eXB, hmac}.
#
# Side effects:
#       Error is raised if decoding is failed for some reason.

proc ::otr::auth::parseRevealSignatureMessage {version data} {
    lassign [::otr::data::decode DATA $data] r data
    lassign [::otr::data::decode DATA $data] encr data
    lassign [::otr::data::decode MAC $data] hmac data
    list $r $encr $hmac
}

# ::otr::auth::createSignatureMessage --
#
#       Assemble the fourth OTR AKE message which Alice returns to Bob - the
#       signature one.
#
# Arguments:
#       version         The protocol version (2 or 3).
#       cp              AES key computed from the shared secret.
#       m1p             MAC key computed from the shared secret.
#       m2p             MAC key computed from the shared secret.
#       y               The random value which is the part of shared secret.
#       gx              The part of D-H shared secret.
#       keyA            Long-term private key.
#       keyidA          Serial number for the D-H key.
#       -sinstance num  (only for version 3) The sender instance tag.
#       -rinstance num  (only for version 3) The receiver instance tag.
#
# Result:
#       BASE64 encoded OTR packet with the message inside.
#
# Side effects:
#       None.

proc ::otr::auth::createSignatureMessage {version cp m1p m2p y gx \
                                          keyA keyidA args} {
    set sinstance 0x100
    set rinstance 0
    foreach {key val} $args {
        switch -- $key {
            -sinstance { set sinstance $val }
            -rinstance { set rinstance $val }
        }
    }
    set res ""
    append res [::otr::data::encode SHORT $version]
    append res [::otr::data::encode BYTE 0x12]
    if {$version > 2} {
        append res [::otr::data::encode INT $sinstance]
        append res [::otr::data::encode INT $rinstance]
    }

    set gy [::otr::crypto::DHGx $y]
    set gympi [::otr::data::encode MPI $gy]
    set gxmpi [::otr::data::encode MPI $gx]
    set pubA [::otr::data::encode PUBKEY $keyA]
    set ikeyidA [::otr::data::encode INT $keyidA]
    set MA [::sha2::hmac -bin -key $m1p $gympi$gxmpi$pubA$ikeyidA]
    set sigAMA [::otr::data::encode SIG [::otr::crypto::sign $MA $keyA]]
    set encr [::otr::crypto::aes $pubA$ikeyidA$sigAMA $cp 0]
    set encrfield [::otr::data::encode DATA $encr]
    append res $encrfield
    append res \
           [::otr::data::encode MAC [::sha2::hmac -bin -key $m2p $encrfield]]
    ::otr::data::encodeMessage $res
}

# ::otr::auth::processSignatureMessage --
#
#       Pick the received Signature message without the protocol version,
#       packet type and instance tags, as well as the got earlier
#       peer's D-H public key, D-H private key, and create a suitable reply
#       message (which is always empty).
#
# Arguments:
#       version         OTR protocol version.
#       authstate       AKE authentication state.
#       msgstate        Message state.
#       data            Signature packet to process.
#       gy              Peer's D-H public key.
#       x               D-H private key which is to be used for decrypting,
#                       verifying the hash and creating the answer.
#       -sinstance num  (only for version 3) The sender instance tag.
#       -rinstance num  (only for version 3) The receiver instance tag.
#
# Result:
#       Tuple {auhstate msgstate message} or {authstate msgstate message key
#       gy keyid} with new FSM state, the message to reply, and the peer's
#       DSA public key, and serial of the D-H public key to store if the
#       current FSM state is appropriate.
#
# Side effects:
#       None.

proc ::otr::auth::processSignatureMessage {version authstate msgstate data
                                           gy x args} {
    set sinstance 0x100
    set rinstance 0x100
    foreach {key val} $args {
        switch -- $key {
            -sinstance { set sinstance $val }
            -rinstance { set rinstance $val }
        }
    }

    if {[catch {parseSignatureMessage $version $data} res]} {
        return [list $authstate $msgstate ""]
    }

    lassign $res encr hmac

    switch -- $authstate {
        AUTHSTATE_NONE -
        AUTHSTATE_AWAITING_DHKEY -
        AUTHSTATE_AWAITING_REVEALSIG {
            return [list $authstate $msgstate ""]
        }
        AUTHSTATE_AWAITING_SIG {
            lassign [::otr::crypto::AKEKeys $gy $x] ssid c cp m1 m2 m1p m2p
            set myhmac \
                [::sha2::hmac -bin -key $m2p [::otr::data::encode DATA $encr]]
            if {[string range $myhmac 0 19] ne $hmac} {
                #puts "HMAC in Signature message is invalid"
                return [list $authstate $msgstate ""]
            }

            set decr [::otr::crypto::aes $encr $cp 0]
            if {[catch {parsePubkey $decr} res]} {
                # puts "DSA public key can't be deciphered"
                return [list $authstate $msgstate ""]
            }
            lassign $res keyA keyidA sigAMA

            set gympi [::otr::data::encode MPI $gy]
            set gxmpi [::otr::data::encode MPI [::otr::crypto::DHGx $x]]
            set pubA [::otr::data::encode PUBKEY $keyA]
            set ikeyidA [::otr::data::encode INT $keyidA]
            set MA [::sha2::hmac -bin -key $m1p $gympi$gxmpi$pubA$ikeyidA]

            if {![::otr::crypto::verify $MA $sigAMA $keyA]} {
                #puts "DSA signature in Signature message is invalid"
                return [list $authstate $msgstate ""]
            }

            set authstate AUTHSTATE_NONE
            set msgstate MSGSTATE_ENCRYPTED

            return [list $authstate $msgstate "" $keyA $keyidA]
        }
    }
}

# ::otr::auth::parseSignatureMessage --
#
#       Parse the signature message (without version and packet type
#       which were extracted earlier).
#
# Arguments:
#       version         The protocol version (2 or 3).
#       data            The payload to parse.
#
# Result:
#       Tuple {eXB, hmac}.
#
# Side effects:
#       Error is raised if decoding is failed for some reason.

proc ::otr::auth::parseSignatureMessage {version data} {
    lassign [::otr::data::decode DATA $data] encr data
    lassign [::otr::data::decode MAC $data] hmac data
    list $encr $hmac
}

# ::otr::auth::parsePubkey --
#
#       Parse the pubkey, keyid, signature data block.
#
# Arguments:
#       data            The data to parse.
#
# Result:
#       Tuple {pubkey, keyid, signature}.
#
# Side effects:
#       Error is raised if decoding is failed for some reason.

proc ::otr::auth::parsePubkey {data} {
    lassign [::otr::data::decode PUBKEY $data] key data
    lassign [::otr::data::decode INT $data] keyid data
    lassign [::otr::data::decode SIG $data] sig
    list $key $keyid $sig
}

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