# Original source taken from tcllib1.14RC0

proc ::pki::pkcs::parse_key {key {password ""}} {
	array set parsed_key [::pki::_parse_pem $key "-----BEGIN RSA PRIVATE KEY-----" "-----END RSA PRIVATE KEY-----" $password]

	set key_seq $parsed_key(data)

	::asn::asnGetSequence key_seq key
	::asn::asnGetBigInteger key version
	::asn::asnGetBigInteger key ret(n)
	::asn::asnGetBigInteger key ret(e)
	::asn::asnGetBigInteger key ret(d)
	::asn::asnGetBigInteger key ret(p)
	::asn::asnGetBigInteger key ret(q)
	::asn::asnGetBigInteger key ret(e1)
	::asn::asnGetBigInteger key ret(e2)
	::asn::asnGetBigInteger key ret(c)

	set ret(n) [::math::bignum::tostr $ret(n)]
	set ret(e) [::math::bignum::tostr $ret(e)]
	set ret(d) [::math::bignum::tostr $ret(d)]
	set ret(p) [::math::bignum::tostr $ret(p)]
	set ret(q) [::math::bignum::tostr $ret(q)]
	set ret(e1) [::math::bignum::tostr $ret(e1)]
	set ret(e2) [::math::bignum::tostr $ret(e2)]
	set ret(c) [::math::bignum::tostr $ret(c)]
	set ret(l) [expr {int([::pki::_bits $ret(n)] / 8) * 8}]
	set ret(type) rsa

	return [array get ret]
}

proc ::pki::rsa::generate {bitlength {exponent 0x10001}} {
	set e $exponent

	# Step 1. Pick 2 numbers that when multiplied together will give a number with the appropriate length
	set componentbitlen [expr {$bitlength / 2}]

	set p 0
	set q 0
	while 1 {
		set plen [::pki::_bits $p]
		set qlen [::pki::_bits $q]
		if {$plen >= $componentbitlen && $qlen >= $componentbitlen} {
			break
		}

		set x [::pki::_random]
		set y [::pki::_random]

		set xlen [expr {[::pki::_bits $x] / 2}]
		set ylen [expr {[::pki::_bits $y] / 2}]

		set xmask [expr {(1 << $xlen) - 1}]
		set ymask [expr {(1 << $ylen) - 1}]

		set plen [::pki::_bits $p]
		set qlen [::pki::_bits $q]

		if {$plen < $componentbitlen} {
			set p [expr {($p << $xlen) + ($x & $xmask)}]
		}
		if {$qlen < $componentbitlen} {
			set q [expr {($q << $ylen) + ($y & $ymask)}]
		}
	}

	set bitmask [expr {(1 << $componentbitlen) - 1}]
	set p [expr {$p & $bitmask}]
	set q [expr {$q & $bitmask}]

	# Step 2. Verify that "p" and "q" are useful
	## Step 2.a. Verify that they are not too close
	### Where "too close" is defined as 2*n^(1/4)
	set quadroot_of_n [expr {isqrt(isqrt($p * $q))}]
	set min_distance [expr {2 * $quadroot_of_n}]
	set distance [expr {abs($p - $q)}]

	if {$distance < $min_distance} {
		#### Try again.

		return [::pki::rsa::generate $bitlength $exponent]
	}

	# Step 3. Convert the numbers into prime numbers
	if {$p % 2 == 0} {
		incr p -1
	}
	while {![::pki::_isprime $p]} {
		incr p -2
	}

	if {$q % 2 == 0} {
		incr q -1
	}
	while {![::pki::_isprime $q]} {
		incr q -2
	}

	# Step 4. Compute N by multiplying P and Q
	set n [expr {$p * $q}]
	set retkey(n) $n

	set retkey(p) $p
	set retkey(q) $q

	# Step 5. Compute D ...
	## Step 5.a. Generate D
	set d [::pki::rsa::_generate_private $p $q $e $bitlength]
	set retkey(d) $d

	## Step 5.b. Verify D is large enough
	### Verify that D is greater than (1/3)*n^(1/4) 
	set quadroot_of_n [expr {isqrt(isqrt($n))}]
	set min_d [expr {$quadroot_of_n / 3}]
	if {$d < $min_d} {
		#### Try again.

		return [::pki::rsa::generate $bitlength $exponent]
	}

	# Step 6. Compute Exponents 1 and 2 ...
	set retkey(e1) [expr $retkey(d) % ($p-1)]
	set retkey(e2) [expr $retkey(d) % ($q-1)]

	# Step 7. Compute Coefficient ...
	set d 1
	set r $q
	for {set c 0} {$c < $p} {incr c} {
		set d [expr (($p / $r + 1) * $d) % $p]
		set r [expr ($d * $q) % $p]
		if {$r == 1} {
			set retkey(c) $d
			break
		}
	}

	# Step 8. Encode key information
	set retkey(type) rsa
	set retkey(e) $e
	set retkey(l) $bitlength

	return [array get retkey]
}

proc ::pki::x509::privkey2pem {k {pass ""}} {
	array set key $k

	set begin [list]
	lappend begin "-----BEGIN RSA PRIVATE KEY-----"

	set privkey [::asn::asnSequence \
		[::asn::asnBigInteger [::math::bignum::fromstr 0]] \
		[::asn::asnBigInteger [::math::bignum::fromstr $key(n)]] \
		[::asn::asnBigInteger [::math::bignum::fromstr $key(e)]] \
		[::asn::asnBigInteger [::math::bignum::fromstr $key(d)]] \
		[::asn::asnBigInteger [::math::bignum::fromstr $key(p)]] \
		[::asn::asnBigInteger [::math::bignum::fromstr $key(q)]] \
		[::asn::asnBigInteger [::math::bignum::fromstr $key(e1)]] \
		[::asn::asnBigInteger [::math::bignum::fromstr $key(e2)]] \
		[::asn::asnBigInteger [::math::bignum::fromstr $key(c)]] \
	]

	if {$pass != ""} {
		package require des

		set iv ""
		while {[string length $iv] < 16} {
		  append iv [string index 01234567890ABCDEF [expr int(rand() * 16)]]
		}

		lappend begin "Proc-Type: 4,ENCRYPTED"
		lappend begin "DEK-Info: DES-EDE3-CBC,$iv"
		lappend begin {}

		set iv [binary format H* $iv]
		set password_key [::pki::_getopensslkey $pass $iv 24]

		set privkey [DES::des -dir encrypt -mode cbc -iv $iv -key $password_key $privkey]
	}

	set r [::pki::_encode_pem $privkey [join $begin \n] "-----END RSA PRIVATE KEY-----"]
	return $r
}

