# key.tcl --
#
#       This file is a part of the Off-the-Record messaging protocol
#       implementation. It contains the private DSA key encoding/decoding
#       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 base64
package require asn
package require math::bignum
package require otr::crypto

package provide otr::key 0.1

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

namespace eval ::otr::key {}

# ::otr::key::encodePEM --
#
#       Encode the given DSA private key into PEM format.
#
# Arguments:
#       key         DSA private key {p, q, g, y, x}.
#
# Result:
#       Key in PEM format.
#
# Side effects:
#       None.

proc ::otr::key::encodePEM {key} {
    lassign $key p q g y x
    set ev [::asn::asnInteger 0]
    set ep [::asn::asnBigInteger [::math::bignum::fromstr $p]]
    set eq [::asn::asnBigInteger [::math::bignum::fromstr $q]]
    set eg [::asn::asnBigInteger [::math::bignum::fromstr $g]]
    set ey [::asn::asnBigInteger [::math::bignum::fromstr $y]]
    set ex [::asn::asnBigInteger [::math::bignum::fromstr $x]]
    set seq [::asn::asnSequence $ev $ep $eq $eg $ey $ex]
    format \
        "-----BEGIN DSA PRIVATE KEY-----\n%s\n-----END DSA PRIVATE KEY-----" \
        [::base64::encode -maxlen 64 $seq]
}

# ::otr::key::decodePEM --
#
#       Decode the diven DSA private key from PEM format.
#
# Arguments:
#       data            PEM encoded DSA private key.
#
# Result:
#       DSA private key {p, q, g, y, x}.
#
# Side effects:
#       Error is raised if decoding is failed.

proc ::otr::key::decodePEM {data} {
    if {![regexp -- \
       {-----BEGIN DSA PRIVATE KEY-----\n(.*)\n-----END DSA PRIVATE KEY-----} \
                 $data -> base64]} {
        return -code error "Incorrect DSA private key PEM data"
    }
    set binary [::base64::decode $base64]
    ::asn::asnGetSequence binary sequence
    ::asn::asnGetInteger sequence version
    if {$version != 0} {
        return -code error "Unsupported DSA private key PEM version"
    }
    ::asn::asnGetBigInteger sequence bp
    set p [::math::bignum::tostr $bp]
    ::asn::asnGetBigInteger sequence bq
    set q [::math::bignum::tostr $bq]
    ::asn::asnGetBigInteger sequence bg
    set g [::math::bignum::tostr $bg]
    ::asn::asnGetBigInteger sequence by
    set y [::math::bignum::tostr $by]
    ::asn::asnGetBigInteger sequence bx
    set x [::math::bignum::tostr $bx]
    if {[::otr::crypto::BitLength $p] != 1024 &&
                    [::otr::crypto::BitLength $q] != 160} {
        return -code error \
               "Unsupported DSA private key length, must be 1024 bit"
    }
    list $p $q $g $y $x
}

# ::otr::key::readPEM --
#
#       Read and decode DSA private key from a file in PEM format.
#
# Arguments:
#       filename        Filename with the key to read.
#
# Result:
#       DSA private key {p, q, g, y, x}.
#
# Side effects:
#       Error is raised if file can't be read or decoding is failed.

proc ::otr::key::readPEM {filename} {
    set fd [open $filename]
    set data [read $fd]
    close $fd
    decodePEM $data
}

# ::otr::key::writePEM --
#
#       Encode DSA private key to PEM format and write it to a file.
#
# Arguments:
#       key             DSA private key {p, q, g, y, x}.
#       filename        Filename to write.
# Result:
#       Empty string.
#
# Side effects:
#       Error may be raised if the file can't be written.

proc ::otr::key::writePEM {key filename} {
    set fd [open $filename wb]
    set data [encodePEM $key]
    puts $fd $data
    close $fd
}

# ::otr::key::generate --
#
#       Generate a new DSA private key.
#
# Arguments:
#       L           Length of p in bits.
#       N           Length of q in bits.
#
# Result:
#       DSA private key {p, q, g, y, x}.
#
# Side effects:
#       Several random numbers are generated, so some entropy is used
#       by PRNG.

proc ::otr::key::generate {L N} {
    set status 0
    while {!$status} {
        # Generate first seed
        set fstatus 0
        while {!$fstatus} {
            lassign [::otr::crypto::getFirstSeed $N $N] \
                    fstatus firstseed
        }

        # Generate p and q
        lassign [::otr::crypto::genPrimes $L $N $firstseed] \
                status p q pseed qseed pgen_counter qgen_counter
    }

    # Generate g
    set g [::otr::crypto::genGenerator $p $q]

    # Generate x y
    set kstatus 0
    while {!$kstatus} {
        lassign [::otr::crypto::genKeyPair $p $q $g] kstatus x y
    }

    list $p $q $g $y $x
}

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