mirror of
https://github.com/odin-lang/Odin.git
synced 2026-07-12 10:59:33 +00:00
core/cryto/rsa: Initial import
This commit is contained in:
7
core/crypto/rsa/doc.odin
Normal file
7
core/crypto/rsa/doc.odin
Normal file
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
RSA (Rivest–Shamir–Adleman) cryptosystem.
|
||||
|
||||
See:
|
||||
- [[ https://www.rfc-editor.org/info/rfc8017/ ]]
|
||||
*/
|
||||
package rsa
|
||||
444
core/crypto/rsa/rsa.odin
Normal file
444
core/crypto/rsa/rsa.odin
Normal file
@@ -0,0 +1,444 @@
|
||||
package rsa
|
||||
|
||||
// Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
|
||||
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import "core:bytes"
|
||||
import "core:crypto"
|
||||
import subtle "core:crypto/_subtle"
|
||||
import "core:encoding/endian"
|
||||
|
||||
// Minimum size for a RSA modulus (in bits).
|
||||
//
|
||||
// Note: 1024-bits is arguably insufficient as of this writing, with
|
||||
// 2048-bits being a more sensible value, however 1024-bits is likely
|
||||
// still in frequent enough use.
|
||||
//
|
||||
// Note: CA signed TLS certificates have a strict requirement of a modulus
|
||||
// size that is at least 2048-bits [[ https://cabforum.org/working-groups/server/baseline-requirements/documents/]].
|
||||
MODULUS_MIN_SIZE :: 1024
|
||||
|
||||
// Maximum size for a RSA modulus (in bits).
|
||||
//
|
||||
// This value MUST be a multiple of 64. This value MUST NOT exceed 47666
|
||||
// (some computations in RSA key generation rely on the factor size being
|
||||
// no more than 23833 bits). RSA key sizes beyond 3072 bits don't make a
|
||||
// lot of sense anyway.
|
||||
MODULUS_MAX_SIZE :: 4096
|
||||
|
||||
// Maxmimum size for a RSA public exponent (in bits).
|
||||
//
|
||||
// Note: This implementation supports arbitrary size exponents, however
|
||||
// limit it to something sensible (some implementations are known to
|
||||
// choke on exponents >= 2^32), with the most common choice being
|
||||
// `65537`.
|
||||
EXPONENT_MAX_SIZE :: 32
|
||||
|
||||
// Maximum size for a RSA factor (in bits). This is for RSA private-key
|
||||
// operations. Default is to support factors up to a bit more than half
|
||||
// the maximum modulus size.
|
||||
//
|
||||
// This value MUST be a multiple of 32.
|
||||
FACTOR_MAX_SIZE :: (MODULUS_MAX_SIZE + 64) >> 1
|
||||
|
||||
// Default size for a RSA key (in bits).
|
||||
DEFAULT_MODULUS_SIZE :: 2048
|
||||
|
||||
// RSA public exponent used for key generation. This MUST be a prime
|
||||
// number greater than 2.
|
||||
@(private)
|
||||
PUBLIC_EXPONENT :: 65537
|
||||
|
||||
#assert(EXPONENT_MAX_SIZE <= 32)
|
||||
|
||||
// Private_Key is a RSA private key.
|
||||
Private_Key :: struct {
|
||||
_pub_key: Public_Key,
|
||||
_d: Modulus, // Private exponent has the same size as n.
|
||||
_p: Factor,
|
||||
_q: Factor,
|
||||
|
||||
// CRT coefficients.
|
||||
_dp: Factor, // d % (p - 1)
|
||||
_dq: Factor, // d % (q - 1)
|
||||
_iq: Factor, // q^(-1) mod p
|
||||
|
||||
_is_initialized: bool,
|
||||
}
|
||||
|
||||
// Public_Key is a RSA public key.
|
||||
Public_Key :: struct {
|
||||
_n: Modulus,
|
||||
_e: u32,
|
||||
_is_initialized: bool,
|
||||
}
|
||||
|
||||
// private_key_generate uses the system entropy source to generate a new
|
||||
// Private_Key. The key size is specified in bits, and must be a multiple
|
||||
// of 8.
|
||||
@(require_results)
|
||||
private_key_generate :: proc(priv_key: ^Private_Key, key_size := DEFAULT_MODULUS_SIZE) -> bool {
|
||||
if !crypto.HAS_RAND_BYTES {
|
||||
return false
|
||||
}
|
||||
if key_size < MODULUS_MIN_SIZE || key_size > MODULUS_MAX_SIZE {
|
||||
return false
|
||||
}
|
||||
if key_size % 8 != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
private_key_clear(priv_key)
|
||||
defer if !priv_key._is_initialized {
|
||||
private_key_clear(priv_key)
|
||||
}
|
||||
|
||||
for {
|
||||
// The only way this can fail is if we get extremely unlucky
|
||||
// and we fail to derive `iq` (1/d mod p).
|
||||
if keygen_inner(priv_key, key_size) == 1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
priv_key._is_initialized = true
|
||||
priv_key._pub_key._is_initialized = true
|
||||
|
||||
// Self-test the key.
|
||||
priv_key._is_initialized = pkcs1_sig_selftest(priv_key)
|
||||
|
||||
return priv_key._is_initialized
|
||||
}
|
||||
|
||||
// private_key_n copies the private key's public modulus to dst if dst is
|
||||
// non-nil and of sufficient size, and returns the number of bytes
|
||||
// copied/would be copied (ie: calling with `dst = nil` gets the required
|
||||
// size).
|
||||
@(require_results)
|
||||
private_key_n :: proc(priv_key: ^Private_Key, dst: []byte) -> (n_len: int) {
|
||||
ensure(priv_key._is_initialized, "crypto/rsa: uninitialized private key")
|
||||
|
||||
return public_key_n(&priv_key._pub_key, dst)
|
||||
}
|
||||
|
||||
// private_key_e returns the private key's public exponent as a u32.
|
||||
@(require_results)
|
||||
private_key_e :: proc(priv_key: ^Private_Key) -> u32 {
|
||||
ensure(priv_key._is_initialized, "crypto/rsa: uninitialized private key")
|
||||
|
||||
return public_key_e(&priv_key._pub_key)
|
||||
}
|
||||
|
||||
// private_key_d copies the private key's private exponent `d` to dst if
|
||||
// dst is non-nil and of sufficient size, and returns the number of bytes
|
||||
// copied/would be copied (ie: calling with `dst = nil` gets the required
|
||||
// size).
|
||||
//
|
||||
// Note: The data returned MUST be kept confidential.
|
||||
@(require_results)
|
||||
private_key_d :: proc(priv_key: ^Private_Key, dst: []byte) -> (n_len: int) {
|
||||
ensure(priv_key._is_initialized, "crypto/rsa: uninitialized private key")
|
||||
|
||||
return modulus_copyout(&priv_key._d, dst)
|
||||
}
|
||||
|
||||
// private_key_p copies the private key's first prime factor `p` to dst
|
||||
// if dst is non-nil and of sufficient size, and returns the number of
|
||||
// bytes copied/would be copied (ie: calling with `dst = nil` gets the
|
||||
// required size).
|
||||
//
|
||||
// Note: The data returned MUST be kept confidential.
|
||||
@(require_results)
|
||||
private_key_p :: proc(priv_key: ^Private_Key, dst: []byte) -> (n_len: int) {
|
||||
ensure(priv_key._is_initialized, "crypto/rsa: uninitialized private key")
|
||||
|
||||
return factor_copyout(&priv_key._p, dst)
|
||||
}
|
||||
|
||||
// private_key_q copies the private key's second prime factor `q` to dst
|
||||
// if dst is non-nil and of sufficient size, and returns the number of
|
||||
// bytes copied/would be copied (ie: calling with `dst = nil` gets the
|
||||
// required size).
|
||||
//
|
||||
// Note: The data returned MUST be kept confidential.
|
||||
@(require_results)
|
||||
private_key_q :: proc(priv_key: ^Private_Key, dst: []byte) -> (n_len: int) {
|
||||
ensure(priv_key._is_initialized, "crypto/rsa: uninitialized private key")
|
||||
|
||||
return factor_copyout(&priv_key._q, dst)
|
||||
}
|
||||
|
||||
// private_key_dp copies the private key's first reduced exponent
|
||||
// `d % (p-1)` to dst if dst is non-nil and of sufficient size, and
|
||||
// returns the number of bytes copied/would be copied (ie: calling with
|
||||
//`dst = nil` gets the required size).
|
||||
//
|
||||
// Note: The data returned MUST be kept confidential.
|
||||
@(require_results)
|
||||
private_key_dp :: proc(priv_key: ^Private_Key, dst: []byte) -> (n_len: int) {
|
||||
ensure(priv_key._is_initialized, "crypto/rsa: uninitialized private key")
|
||||
|
||||
return factor_copyout(&priv_key._dp, dst)
|
||||
}
|
||||
|
||||
// private_key_dq copies the private key's second reduced exponent
|
||||
// `d % (q-1)` to dst if dst is non-nil and of sufficient size, and
|
||||
// returns the number of bytes copied/would be copied (ie: calling with
|
||||
//`dst = nil` gets the required size).
|
||||
//
|
||||
// Note: The data returned MUST be kept confidential.
|
||||
@(require_results)
|
||||
private_key_dq :: proc(priv_key: ^Private_Key, dst: []byte) -> (n_len: int) {
|
||||
ensure(priv_key._is_initialized, "crypto/rsa: uninitialized private key")
|
||||
|
||||
return factor_copyout(&priv_key._dq, dst)
|
||||
}
|
||||
|
||||
// private_key_iq copies the private key's CRT coefficient `iq` to dst if
|
||||
// dst is non-nil and of sufficient size, and returns the number of bytes
|
||||
// copied/would be copied (ie: calling with`dst = nil` gets the required
|
||||
// size).
|
||||
//
|
||||
// Note: The data returned MUST be kept confidential.
|
||||
@(require_results)
|
||||
private_key_iq :: proc(priv_key: ^Private_Key, dst: []byte) -> (n_len: int) {
|
||||
ensure(priv_key._is_initialized, "crypto/rsa: uninitialized private key")
|
||||
|
||||
return factor_copyout(&priv_key._iq, dst)
|
||||
}
|
||||
|
||||
// private_key_size returns the size of the private key's public modulus
|
||||
// in bytes. All ciphertexts and signatures will also be this size.
|
||||
@(require_results)
|
||||
private_key_size :: proc(priv_key: ^Private_Key) -> int {
|
||||
ensure(priv_key._is_initialized, "crypto/rsa: uninitialized private key")
|
||||
|
||||
return priv_key._pub_key._n.v_len
|
||||
}
|
||||
|
||||
// private_key_set_bytes sets a private key from byte-encoded components,
|
||||
// and returns true if and only if (⟺) the operation was successful.
|
||||
//
|
||||
// Note: All values are mandatory, and match the values included in the
|
||||
// PKCS private key format.
|
||||
//
|
||||
// WARNING: This routine validates that it is possible to sign/verify with
|
||||
// the deserialized values, however d is not checked at all, nor is the
|
||||
// primality of p and q.
|
||||
@(require_results)
|
||||
private_key_set_bytes :: proc(
|
||||
priv_key: ^Private_Key,
|
||||
n: []byte,
|
||||
e: []byte,
|
||||
d: []byte,
|
||||
p: []byte,
|
||||
q: []byte,
|
||||
dp: []byte,
|
||||
dq: []byte,
|
||||
iq: []byte,
|
||||
) -> bool {
|
||||
private_key_clear(priv_key)
|
||||
defer if !priv_key._is_initialized {
|
||||
private_key_clear(priv_key)
|
||||
}
|
||||
|
||||
if !public_key_set_bytes(&priv_key._pub_key, n, e) {
|
||||
return false
|
||||
}
|
||||
|
||||
if !modulus_set_bytes(&priv_key._d, d) {
|
||||
return false
|
||||
}
|
||||
if !factor_set_bytes(&priv_key._p, p) {
|
||||
return false
|
||||
}
|
||||
if !factor_set_bytes(&priv_key._q, q) {
|
||||
return false
|
||||
}
|
||||
if !factor_set_bytes(&priv_key._dp, dp) {
|
||||
return false
|
||||
}
|
||||
if !factor_set_bytes(&priv_key._dq, dq) {
|
||||
return false
|
||||
}
|
||||
if !factor_set_bytes(&priv_key._iq, iq) {
|
||||
return false
|
||||
}
|
||||
|
||||
priv_key._is_initialized = true
|
||||
|
||||
// Test the key.
|
||||
//
|
||||
// Note: This DOES NOT check that p/q are prime and if d is
|
||||
// consistent (as it is not used by our implementation).
|
||||
priv_key._is_initialized = pkcs1_sig_selftest(priv_key)
|
||||
|
||||
return priv_key._is_initialized
|
||||
}
|
||||
|
||||
// private_key_set sets priv_key to src.
|
||||
private_key_set :: proc(priv_key, src: ^Private_Key) {
|
||||
if src == nil || !src._is_initialized {
|
||||
private_key_clear(priv_key)
|
||||
return
|
||||
}
|
||||
|
||||
public_key_set(&priv_key._pub_key, &src._pub_key)
|
||||
modulus_set(&priv_key._d, &src._d)
|
||||
factor_set(&priv_key._p, &src._p)
|
||||
factor_set(&priv_key._q, &src._q)
|
||||
factor_set(&priv_key._dp, &src._dp)
|
||||
factor_set(&priv_key._dq, &src._dq)
|
||||
factor_set(&priv_key._iq, &src._iq)
|
||||
|
||||
priv_key._is_initialized = true
|
||||
}
|
||||
|
||||
// private_key_equal returns true if and only if (⟺) priv_key is equal to other.
|
||||
@(require_results)
|
||||
private_key_equal :: proc(priv_key, other: ^Private_Key) -> bool {
|
||||
ensure(priv_key._is_initialized && other._is_initialized, "crypto/rsa: uninitialized private key")
|
||||
|
||||
pk_eq := public_key_equal(&priv_key._pub_key, &other._pub_key)
|
||||
|
||||
eq := crypto.compare_constant_time(modulus_bytes(&priv_key._d), modulus_bytes(&other._d))
|
||||
eq &= crypto.compare_constant_time(factor_bytes(&priv_key._p), factor_bytes(&other._p))
|
||||
eq &= crypto.compare_constant_time(factor_bytes(&priv_key._q), factor_bytes(&other._q))
|
||||
eq &= crypto.compare_constant_time(factor_bytes(&priv_key._dp), factor_bytes(&other._dp))
|
||||
eq &= crypto.compare_constant_time(factor_bytes(&priv_key._dq), factor_bytes(&other._dq))
|
||||
eq &= crypto.compare_constant_time(factor_bytes(&priv_key._iq), factor_bytes(&other._iq))
|
||||
|
||||
return pk_eq & (eq == 1)
|
||||
}
|
||||
|
||||
// private_key_clear clears priv_key to the uninitialized state.
|
||||
private_key_clear :: proc "contextless" (priv_key: ^Private_Key) {
|
||||
crypto.zero_explicit(priv_key, size_of(Private_Key))
|
||||
}
|
||||
|
||||
// public_key_n copies the public key's modulus `n` to dst if dst is
|
||||
// non-nil and of sufficient size, and returns the number of bytes
|
||||
// copied/would be copied (ie: calling with `dst = nil` gets the
|
||||
// required size).
|
||||
@(require_results)
|
||||
public_key_n :: proc(pub_key: ^Public_Key, dst: []byte) -> (n_len: int) {
|
||||
ensure(pub_key._is_initialized, "crypto/rsa: uninitialized public key")
|
||||
|
||||
return modulus_copyout(&pub_key._n, dst)
|
||||
}
|
||||
|
||||
// public_key_e returns the public key's exponent `e` as a u32.
|
||||
@(require_results)
|
||||
public_key_e :: proc(pub_key: ^Public_Key) -> u32 {
|
||||
ensure(pub_key._is_initialized, "crypto/rsa: uninitialized public key")
|
||||
|
||||
return pub_key._e
|
||||
}
|
||||
|
||||
// public_key_size returns the size of the public key's modulus in bytes.
|
||||
// All ciphertexts and signatures will also be this size.
|
||||
@(require_results)
|
||||
public_key_size :: proc(pub_key: ^Public_Key) -> int {
|
||||
ensure(pub_key._is_initialized, "crypto/rsa: uninitialized public key")
|
||||
|
||||
return pub_key._n.v_len
|
||||
}
|
||||
|
||||
// public_key_set_bytes sets a public key from byte-encoded components,
|
||||
// and returns true if and only if (⟺) the operation was successful.
|
||||
@(require_results)
|
||||
public_key_set_bytes :: proc(pub_key: ^Public_Key, n, e: []byte) -> bool {
|
||||
public_key_clear(pub_key)
|
||||
defer if !pub_key._is_initialized {
|
||||
public_key_clear(pub_key)
|
||||
}
|
||||
|
||||
ok := modulus_set_bytes(&pub_key._n, n)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if modulus_len(&pub_key._n) < MODULUS_MIN_SIZE >> 3 {
|
||||
return false
|
||||
}
|
||||
if !modulus_is_odd(&pub_key._n) {
|
||||
return false
|
||||
}
|
||||
|
||||
e_ := bytes.trim_left(e, []byte{0x00})
|
||||
e_len := len(e_)
|
||||
if e_len > EXPONENT_MAX_SIZE >> 3 {
|
||||
return false
|
||||
}
|
||||
e_buf: [4]byte
|
||||
copy(e_buf[4 - e_len:], e)
|
||||
e_u32 := endian.unchecked_get_u32be(e_buf[:])
|
||||
if e_u32 < 3 || e_u32 & 1 == 0 {
|
||||
return false
|
||||
}
|
||||
pub_key._e = e_u32
|
||||
|
||||
pub_key._is_initialized = true
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// public_key_set sets pub_key to src.
|
||||
public_key_set :: proc(pub_key, src: ^Public_Key) {
|
||||
if src == nil || !src._is_initialized {
|
||||
public_key_clear(pub_key)
|
||||
return
|
||||
}
|
||||
|
||||
modulus_set(&pub_key._n, &src._n)
|
||||
pub_key._e = src._e
|
||||
pub_key._is_initialized = true
|
||||
}
|
||||
|
||||
// public_key_set_priv sets pub_key to the public component of priv_key.
|
||||
public_key_set_priv :: proc(pub_key: ^Public_Key, priv_key: ^Private_Key) {
|
||||
ensure(priv_key._is_initialized, "crypto/rsa: uninitialized private key")
|
||||
pub_key^ = priv_key._pub_key
|
||||
}
|
||||
|
||||
// public_key_equal returns true if and only if (⟺) pub_key is equal to other.
|
||||
public_key_equal :: proc(pub_key, other: ^Public_Key) -> bool {
|
||||
ensure(pub_key._is_initialized && other._is_initialized, "crypto/rsa: uninitialized public key")
|
||||
|
||||
eq := crypto.compare_constant_time(modulus_bytes(&pub_key._n), modulus_bytes(&other._n))
|
||||
eq &= int(subtle.eq(pub_key._e, other._e))
|
||||
|
||||
return eq == 1
|
||||
}
|
||||
|
||||
// public_key_clear clears pub_key to the uninitialized state.
|
||||
public_key_clear :: proc "contextless" (pub_key: ^Public_Key) {
|
||||
crypto.zero_explicit(pub_key, size_of(Public_Key))
|
||||
}
|
||||
|
||||
// size returns the size of the key's public modulus in bytes.
|
||||
// All ciphertexts and signatures will also be this size.
|
||||
size :: proc "contextless" (key: ^$T) -> int where T == Private_Key || T == Private_Key {
|
||||
when T == Private_Key {
|
||||
return private_key_size(key)
|
||||
} else {
|
||||
return public_key_size(key)
|
||||
}
|
||||
}
|
||||
197
core/crypto/rsa/rsa_dec_oaep.odin
Normal file
197
core/crypto/rsa/rsa_dec_oaep.odin
Normal file
@@ -0,0 +1,197 @@
|
||||
package rsa
|
||||
|
||||
// Copyright (c) 2018 Thomas Pornin <pornin@bolet.org>
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
|
||||
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import "core:crypto"
|
||||
import subtle "core:crypto/_subtle"
|
||||
import "core:crypto/hash"
|
||||
|
||||
// decrypt_oaep returns the plaintext and true if and only if (⟺) it
|
||||
// successfully decrypts the ciphertext with OAEP parameterized by
|
||||
// label, hash_algo, and mgf1_algo, and writes the plaintext into dst.
|
||||
// If mgf1_algo is unspecified, hash_algo will be used.
|
||||
//
|
||||
// Note: dst MUST be large enough to contain the plaintext.
|
||||
@(require_results)
|
||||
decrypt_oaep :: proc(
|
||||
priv_key: ^Private_Key,
|
||||
hash_algo: hash.Algorithm,
|
||||
ciphertext: []byte,
|
||||
dst: []byte,
|
||||
label: []byte = nil,
|
||||
mgf1_algo := hash.Algorithm.Invalid,
|
||||
) -> (plaintext: []byte, ok: bool) {
|
||||
if !priv_key._is_initialized {
|
||||
return
|
||||
}
|
||||
ct_len := len(ciphertext)
|
||||
if ct_len != modulus_len(&priv_key._pub_key._n) {
|
||||
return
|
||||
}
|
||||
if hash_algo == .Invalid {
|
||||
return
|
||||
}
|
||||
mgf1_algo_ := mgf1_algo
|
||||
if mgf1_algo == .Invalid {
|
||||
mgf1_algo_ = hash_algo
|
||||
}
|
||||
|
||||
tmp: [MODULUS_MAX_SIZE >> 3]byte
|
||||
pt_buf := tmp[:ct_len]
|
||||
defer crypto.zero_explicit(raw_data(pt_buf), ct_len)
|
||||
|
||||
copy(pt_buf, ciphertext)
|
||||
r := private_modpow(pt_buf, priv_key)
|
||||
r_, l := oaep_dec_unpad(hash_algo, mgf1_algo_, label, pt_buf)
|
||||
|
||||
// Conditional branches are ok as we are past the padding
|
||||
// verification.
|
||||
if ok = r & r_ == 1; ok {
|
||||
if l <= len(dst) {
|
||||
copy(dst, pt_buf[:l])
|
||||
plaintext = dst[:l]
|
||||
} else {
|
||||
ok = false
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// oaep_max_plaintext_size returns the maximum supported plaintext size
|
||||
// for a given key, with OAEP parameterized by hash_algo and mgf1_algo.
|
||||
// If mgf1_algo is unspecified, hash_algo will be used.
|
||||
@(require_results)
|
||||
oaep_max_plaintext_size :: proc(
|
||||
k: ^$T,
|
||||
hash_algo: hash.Algorithm,
|
||||
mgf1_algo := hash.Algorithm.Invalid,
|
||||
) -> int where T == Private_Key || T == Public_Key {
|
||||
if !k._is_initialized {
|
||||
return 0
|
||||
}
|
||||
if hash_algo == .Invalid {
|
||||
return 0
|
||||
}
|
||||
mgf1_algo_ := mgf1_algo
|
||||
if mgf1_algo == .Invalid {
|
||||
mgf1_algo_ = hash_algo
|
||||
}
|
||||
|
||||
overhead := 2 + hash.DIGEST_SIZES[hash_algo] + hash.DIGEST_SIZES[mgf1_algo_]
|
||||
|
||||
pub_key: ^Public_Key
|
||||
when T == Private_Key {
|
||||
pub_keyk = &k._pub_key
|
||||
} else {
|
||||
pub_key = k
|
||||
}
|
||||
return modulus_len(&k._n) - overhead
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
xor_hash_data :: proc(hash_algo: hash.Algorithm, dst: []byte, src: []byte) {
|
||||
tmp: [hash.MAX_DIGEST_SIZE]byte = ---
|
||||
hash_len := hash.DIGEST_SIZES[hash_algo]
|
||||
digest := tmp[:hash_len]
|
||||
defer crypto.zero_explicit(raw_data(digest), hash_len)
|
||||
|
||||
hash.hash_bytes_to_buffer(hash_algo, src, digest)
|
||||
for v, u in digest {
|
||||
dst[u] ~= v
|
||||
}
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
oaep_dec_unpad :: proc(
|
||||
hash_algo: hash.Algorithm,
|
||||
mgf1_algo: hash.Algorithm,
|
||||
label: []byte,
|
||||
data: []byte,
|
||||
) -> (u32, int) {
|
||||
hash_len := hash.DIGEST_SIZES[hash_algo]
|
||||
k := len(data)
|
||||
buf := data
|
||||
|
||||
// There must be room for the padding.
|
||||
if k < (hash_len << 1) + 2 {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
// Unmask the seed, then the DB value.
|
||||
seed, db := buf[1:1+hash_len], buf[1+hash_len:]
|
||||
mgf1_xor(seed, mgf1_algo, db)
|
||||
mgf1_xor(db, mgf1_algo, seed)
|
||||
|
||||
// Hash the label and XOR it with the value in the array; if
|
||||
// they are equal then these should yield only zeros.
|
||||
xor_hash_data(hash_algo, db, label)
|
||||
|
||||
// At that point, if the padding was correct, when we should
|
||||
// have: 0x00 || seed || 0x00 ... 0x00 0x01 || M
|
||||
// Padding is valid as long as:
|
||||
// - There is at least hlen+1 leading bytes of value 0x00.
|
||||
// - There is at least one non-zero byte.
|
||||
// - The first (leftmost) non-zero byte has value 0x01.
|
||||
//
|
||||
// Ultimately, we may leak the resulting message length, i.e.
|
||||
// the position of the byte of value 0x01, but we must take care
|
||||
// to do so only if the number of zero bytes has been verified
|
||||
// to be at least hlen+1.
|
||||
//
|
||||
// The loop below counts the number of bytes of value 0x00, and
|
||||
// checks that the next byte has value 0x01, in constant-time.
|
||||
//
|
||||
// - If the initial byte (before the seed) is not 0x00, then
|
||||
// r and s are set to 0, and stay there.
|
||||
// - Value r is 1 until the first non-zero byte is reached
|
||||
// (after the seed); it switches to 0 at that point.
|
||||
// - Value s is set to 1 if and only if the data encountered
|
||||
// at the time of the transition of r from 1 to 0 has value
|
||||
// exactly 0x01.
|
||||
// - Value zlen counts the number of leading bytes of value zero
|
||||
// (after the seed).
|
||||
r := u32(subtle.eq(buf[0], 0))
|
||||
s, zlen: u32
|
||||
for u in hash_len + 1..<k {
|
||||
w := u32(buf[u])
|
||||
|
||||
// nz == 1 only for the first non-zero byte.
|
||||
nz := r & ((w + 0xFF) >> 8)
|
||||
s |= nz & subtle.eq(w, 0x01)
|
||||
r &= subtle.not(nz)
|
||||
zlen += r
|
||||
}
|
||||
|
||||
// Padding is correct only if s == 1, _and_ zlen >= hlen.
|
||||
s &= subtle.ge(zlen, u32(hash_len))
|
||||
|
||||
// At that point, padding was verified, and we are now allowed
|
||||
// to make conditional jumps.
|
||||
if s != 0 {
|
||||
plen := 2 + hash_len + int(zlen)
|
||||
k -= plen
|
||||
copy(buf[:k], buf[plen:])
|
||||
}
|
||||
return s, k
|
||||
}
|
||||
55
core/crypto/rsa/rsa_dec_tls_pms.odin
Normal file
55
core/crypto/rsa/rsa_dec_tls_pms.odin
Normal file
@@ -0,0 +1,55 @@
|
||||
package rsa
|
||||
|
||||
// Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
|
||||
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import subtle "core:crypto/_subtle"
|
||||
|
||||
// unsafe_decrypt_tls_pms decrypts a TLS RSA-Encrypted Premaster Secret
|
||||
// Message, unconditionally moves the decrypted plaintext to `data[:48]`,
|
||||
// and returns 1 if and only if (⟺) the operation was successful.
|
||||
//
|
||||
// WARNING: This routine MUST only be used when implementing server-side
|
||||
// support for TLS 1.2's Client Key Exchange message, and extreme care
|
||||
// MUST be taken when handling failures. This key exchange scheme was
|
||||
// removed in TLS 1.3, and not implementing support in the first place
|
||||
// is strongly RECOMMENDED even for TLS 1.2 servers.
|
||||
@(require_results)
|
||||
unsafe_decrypt_tls_pms :: proc(priv_key: ^Private_Key, data: []byte) -> u32 {
|
||||
// A first check on length. Since this test works only on the
|
||||
// buffer length, it needs not (and cannot) be constant-time.
|
||||
_len := len(data)
|
||||
if _len < 59 || _len != priv_key._pub_key._n.v_len {
|
||||
return 0
|
||||
}
|
||||
x := private_modpow(data, priv_key)
|
||||
|
||||
x &= u32(subtle.eq(data[0], 0x00))
|
||||
x &= u32(subtle.eq(data[1], 0x02))
|
||||
for u in 2..<(_len-49) {
|
||||
x &= u32(subtle.neq(data[u], 0))
|
||||
}
|
||||
x &= u32(subtle.eq(data[_len - 49], 0x00))
|
||||
copy(data[:48], data[_len - 48:])
|
||||
|
||||
return x
|
||||
}
|
||||
105
core/crypto/rsa/rsa_enc_oaep.odin
Normal file
105
core/crypto/rsa/rsa_enc_oaep.odin
Normal file
@@ -0,0 +1,105 @@
|
||||
package rsa
|
||||
|
||||
// Copyright (c) 2018 Thomas Pornin <pornin@bolet.org>
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
|
||||
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import "base:intrinsics"
|
||||
import "core:crypto"
|
||||
import "core:crypto/hash"
|
||||
|
||||
// encrypt_oaep returns true if and only if (⟺) it successfully
|
||||
// encrypts the plaintext with OAEP parameterized by label, hash_algo,
|
||||
// and mgf1_algo, and writes the cipherttext into dst. If mgf1_algo is
|
||||
// unspecified, hash_algo will be used.
|
||||
//
|
||||
// This routine will fail if the system entropy source is unavailable.
|
||||
encrypt_oaep :: proc(
|
||||
pub_key: ^Public_Key,
|
||||
hash_algo: hash.Algorithm,
|
||||
plaintext: []byte,
|
||||
dst: []byte,
|
||||
label: []byte = nil,
|
||||
mgf1_algo := hash.Algorithm.Invalid,
|
||||
) -> bool {
|
||||
if !pub_key._is_initialized {
|
||||
return false
|
||||
}
|
||||
if hash_algo == .Invalid {
|
||||
return false
|
||||
}
|
||||
mgf1_algo_ := mgf1_algo
|
||||
if mgf1_algo == .Invalid {
|
||||
mgf1_algo_ = hash_algo
|
||||
}
|
||||
if len(dst) != modulus_len(&pub_key._n) {
|
||||
return false
|
||||
}
|
||||
if len(plaintext) > oaep_max_plaintext_size(pub_key, hash_algo, mgf1_algo_) {
|
||||
return false
|
||||
}
|
||||
|
||||
if oaep_enc_pad(hash_algo, mgf1_algo_, label, dst, plaintext) != 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
return public_modpow(dst, pub_key) == 1
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
oaep_enc_pad :: proc(
|
||||
hash_algo: hash.Algorithm,
|
||||
mgf1_algo: hash.Algorithm,
|
||||
label: []byte,
|
||||
dst: []byte,
|
||||
src: []byte,
|
||||
) -> u32 {
|
||||
hash_len := hash.DIGEST_SIZES[hash_algo]
|
||||
src_len := len(src)
|
||||
k := len(dst)
|
||||
|
||||
// Note: Length checks are handled by the caller.
|
||||
|
||||
// Apply padding. At this point, things cannot fail.
|
||||
buf := dst
|
||||
|
||||
// Assemble: DB = lHash || PS || 0x01 || M
|
||||
// We first place the source message M with copy(), so that
|
||||
// overlaps between source and destination buffers are supported.
|
||||
copy(buf[k - src_len:], src)
|
||||
hash.hash_bytes_to_buffer(hash_algo, label, buf[1+hash_len:1+hash_len << 1])
|
||||
intrinsics.mem_zero(raw_data(buf[1 + hash_len << 1:]), k - src_len - (hash_len << 1) - 2)
|
||||
buf[k - src_len - 1] = 0x01
|
||||
|
||||
// Make the random seed.
|
||||
seed, db := buf[1:1+hash_len], buf[1+hash_len:]
|
||||
crypto.rand_bytes(seed)
|
||||
|
||||
// Mask DB with the mask generated from the seed.
|
||||
mgf1_xor(db, mgf1_algo, seed)
|
||||
|
||||
// Mask the seed with the mask generated from the masked DB.
|
||||
mgf1_xor(seed, mgf1_algo, db)
|
||||
|
||||
// Padding result: EM = 0x00 || maskedSeed || maskedDB.
|
||||
buf[0] = 0x00
|
||||
return 1
|
||||
}
|
||||
110
core/crypto/rsa/rsa_int.odin
Normal file
110
core/crypto/rsa/rsa_int.odin
Normal file
@@ -0,0 +1,110 @@
|
||||
#+private
|
||||
package rsa
|
||||
|
||||
import "core:bytes"
|
||||
|
||||
Big_Int :: struct($N: int) {
|
||||
v: [N]byte,
|
||||
v_len: int,
|
||||
}
|
||||
|
||||
Modulus :: Big_Int(MODULUS_MAX_SIZE >> 3)
|
||||
Factor :: Big_Int(FACTOR_MAX_SIZE >> 3)
|
||||
|
||||
@(require_results)
|
||||
modulus_set_bytes :: proc(n: ^Modulus, b: []byte) -> bool {
|
||||
b_ := bytes.trim_left(b, []byte{0x00})
|
||||
b_len := len(b_)
|
||||
|
||||
if b_len > size_of(n.v) || b_len == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
copy(n.v[:], b_)
|
||||
n.v_len = b_len
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
modulus_set :: proc "contextless" (n, other: ^Modulus) {
|
||||
// Copy the full thing.
|
||||
copy(n.v[:], other.v[:])
|
||||
n.v_len = other.v_len
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
modulus_bytes :: #force_inline proc "contextless" (n: ^Modulus) -> []byte {
|
||||
return n.v[:n.v_len]
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
modulus_len :: #force_inline proc "contextless" (n: ^Modulus) -> int {
|
||||
return n.v_len
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
modulus_copyout :: proc(n: ^Modulus, dst: []byte) -> (n_len: int) {
|
||||
if n_len = modulus_len(n); n_len == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if len(dst) > 0 {
|
||||
ensure(len(dst) >= n_len, "crypto/rsa: insufficent buffer size")
|
||||
copy(dst, modulus_bytes(n))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
modulus_is_odd :: proc "contextless" (n: ^Modulus) -> bool {
|
||||
if n.v_len == 0 || n.v[n.v_len-1] & 1 == 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
factor_set_bytes :: proc(n: ^Factor, b: []byte) -> bool {
|
||||
b_ := bytes.trim_left(b, []byte{0x00})
|
||||
b_len := len(b_)
|
||||
|
||||
if b_len > size_of(n.v) || b_len == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
copy(n.v[:], b_)
|
||||
n.v_len = b_len
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
factor_set :: proc "contextless" (n, other: ^Factor) {
|
||||
// Copy the full thing.
|
||||
copy(n.v[:], other.v[:])
|
||||
n.v_len = other.v_len
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
factor_bytes :: #force_inline proc "contextless" (n: ^Factor) -> []byte {
|
||||
return n.v[:n.v_len]
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
factor_len :: #force_inline proc "contextless" (n: ^Factor) -> int {
|
||||
return n.v_len
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
factor_copyout :: proc(n: ^Factor, dst: []byte) -> (n_len: int) {
|
||||
if n_len = factor_len(n); n_len == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if len(dst) > 0 {
|
||||
ensure(len(dst) >= n_len, "crypto/rsa: insufficent buffer size")
|
||||
copy(dst, factor_bytes(n))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
367
core/crypto/rsa/rsa_keygen.odin
Normal file
367
core/crypto/rsa/rsa_keygen.odin
Normal file
@@ -0,0 +1,367 @@
|
||||
package rsa
|
||||
|
||||
// Copyright (c) 2018 Thomas Pornin <pornin@bolet.org>
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
|
||||
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import "core:crypto"
|
||||
import bigint "core:crypto/_bigint"
|
||||
import subtle "core:crypto/_subtle"
|
||||
import "core:slice"
|
||||
|
||||
// Swap two buffers in RAM. They must be disjoint.
|
||||
@(private="file")
|
||||
bufswap_u32 :: proc "contextless" (b1, b2: []u32) {
|
||||
l := len(b1)
|
||||
|
||||
for u in 0..<l {
|
||||
b1[u], b2[u] = b2[u], b1[u]
|
||||
}
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
keygen_inner :: proc(sk: ^Private_Key, key_size: int) -> u32 {
|
||||
// We need temporary values for at least 7 integers of the same size
|
||||
// as a factor (including header word); more space helps with performance
|
||||
// (in modular exponentiations), but we much prefer to remain under
|
||||
// 2 kilobytes in total, to save stack space. The macro TEMPS below
|
||||
// exceeds 512 (which is a count in 32-bit words) when MODULUS_MAX_SIZE
|
||||
// is greater than 4464 (default value is 4096, so the 2-kB limit is
|
||||
// maintained unless MODULUS_MAX_SIZE was modified).
|
||||
TEMPS :: max(512, ((((7 * ((((MODULUS_MAX_SIZE + 1) >> 1) + 61) / 31))) + 1) >> 1) << 1)
|
||||
|
||||
assert(key_size >= MODULUS_MIN_SIZE && key_size <= MODULUS_MAX_SIZE)
|
||||
|
||||
t64: [TEMPS >> 1]u64
|
||||
t32 := slice.reinterpret([]u32, t64[:])
|
||||
defer crypto.zero_explicit(&t64, size_of(t64))
|
||||
|
||||
esize_p := u32(key_size + 1) >> 1
|
||||
esize_q := u32(key_size) - esize_p
|
||||
sk._p.v_len = int((esize_p + 7) >> 3)
|
||||
sk._q.v_len = int((esize_q + 7) >> 3)
|
||||
sk._dp.v_len = sk._p.v_len
|
||||
sk._dq.v_len = sk._q.v_len
|
||||
sk._iq.v_len = sk._p.v_len
|
||||
|
||||
pk := &sk._pub_key
|
||||
pk._n.v_len = (key_size + 7) >> 3
|
||||
pk._e = PUBLIC_EXPONENT
|
||||
|
||||
sk._d.v_len = pk._n.v_len // Private exponent length is that of the modulus.
|
||||
|
||||
// We now switch to encoded sizes.
|
||||
//
|
||||
// floor((x * 16913) / (2^19)) is equal to floor(x/31) for all
|
||||
// integers x from 0 to 34966; the intermediate product fits on
|
||||
// 30 bits, thus we can use MUL31().
|
||||
esize_p += u32(bigint._mul31(esize_p, 16913) >> 19)
|
||||
esize_q += u32(bigint._mul31(esize_q, 16913) >> 19)
|
||||
plen := (esize_p + 31) >> 5
|
||||
qlen := (esize_q + 31) >> 5
|
||||
p := t32
|
||||
q := p[1 + plen:]
|
||||
t := q[1 + qlen:]
|
||||
|
||||
// Since we use a prime exponent, when searching for candidate primes,
|
||||
// checking if `GCD(e, prime - 1) = 1` is a simple matter of euclidian
|
||||
// division.
|
||||
for {
|
||||
bigint.i62_mkprime(p, esize_p, PUBLIC_EXPONENT, t)
|
||||
p[1] -= 1
|
||||
if bigint.i31_rem(p, PUBLIC_EXPONENT) != 0 {
|
||||
p[1] += 1
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
bigint.i62_mkprime(q, esize_q, PUBLIC_EXPONENT, t)
|
||||
q[1] -= 1
|
||||
if bigint.i31_rem(q, PUBLIC_EXPONENT) != 0 {
|
||||
q[1] += 1
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// If p and q have the same size, then it is possible that q > p
|
||||
// (when the target modulus size is odd, we generate p with a
|
||||
// greater bit length than q). If q > p, we want to swap p and q
|
||||
// for two reasons:
|
||||
// - The final step below (inversion of q modulo p) is easier if
|
||||
// p > q.
|
||||
// - While BearSSL's RSA code is perfectly happy with RSA keys such
|
||||
// that p < q, some other implementations have restrictions and
|
||||
// require p > q.
|
||||
//
|
||||
// Note that we can do a simple non-constant-time swap here,
|
||||
// because the only information we leak here is that we insist on
|
||||
// returning p and q such that p > q, which is not a secret.
|
||||
if esize_p == esize_q && bigint.i31_sub(p, q, 0) == 1 {
|
||||
bufswap_u32(p[:1+plen], q)
|
||||
}
|
||||
|
||||
sk_p, sk_q := factor_bytes(&sk._p), factor_bytes(&sk._q)
|
||||
bigint.i31_encode(sk_p, p)
|
||||
bigint.i31_encode(sk_q, q)
|
||||
// The odds of this happening are infinitesimally small, however
|
||||
// checking for it is cheap.
|
||||
if crypto.compare_constant_time(sk_p, sk_q) == 1 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Compute the public modulus too.
|
||||
bigint.i31_zero(t, p[0])
|
||||
bigint.i31_mulacc(t, p, q)
|
||||
bigint.i31_encode(modulus_bytes(&pk._n), t)
|
||||
|
||||
// Compute the private exponent.
|
||||
//
|
||||
// Computing p - 1 and q - 1 this way is safe as p and q
|
||||
// are guaranteed to be odd, thus the LSB will always be
|
||||
// set.
|
||||
p[1], q[1] = p[1] - 1, q[1] - 1 // p = p - 1, q = q - 1
|
||||
if compute_privexp(sk, p, q, pk._e, t) != 1 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Compute `d % (p - 1)`.
|
||||
d_mod := t[:1+plen]
|
||||
bigint.i31_decode_reduce(d_mod, modulus_bytes(&sk._d), p)
|
||||
bigint.i31_encode(factor_bytes(&sk._dp), d_mod)
|
||||
|
||||
// Compute `d % (q - 1)`.
|
||||
bigint.i31_decode_reduce(d_mod, modulus_bytes(&sk._d), q)
|
||||
bigint.i31_encode(factor_bytes(&sk._dq), d_mod)
|
||||
|
||||
// Compute `q^(-1) mod p`.
|
||||
p[1], q[1] = p[1] + 1, q[1] + 1 // Restore p, q.
|
||||
return compute_qinv(sk, p, q, plen, t)
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
compute_qinv :: proc "contextless" (sk: ^Private_Key, p, q: []u32, plen: u32, t: []u32) -> u32 {
|
||||
// Per Fermat's Little Theorem, `q^(-1) mod p = q^(p-2) mod p`.
|
||||
//
|
||||
// Note: p is guaranteed to be odd as it is a large prime.
|
||||
|
||||
// Compute and encode `p-2`.
|
||||
p_minus_two := t[:1+plen]
|
||||
copy(p_minus_two, p[:1+plen])
|
||||
two := t[1+plen:] // Temporarily use this for 2.
|
||||
bigint.i31_zero(two, p[0])
|
||||
bigint.i31_decode(two, []byte{2})
|
||||
_ = bigint.i31_sub(p_minus_two, two, 1)
|
||||
iq := factor_bytes(&sk._iq) // Temporarily use this for p - 2.
|
||||
bigint.i31_encode(iq, p_minus_two)
|
||||
|
||||
// Enforce 64-bit alignment.
|
||||
t_ := t
|
||||
if len(t_) & 1 != 0 {
|
||||
t_ = t_[1:]
|
||||
}
|
||||
|
||||
m0i := bigint.i31_ninv31(p[1])
|
||||
ret := bigint.i62_modpow_opt_as_i31(q, iq, p, m0i, t)
|
||||
if ret != 0 {
|
||||
bigint.i31_encode(iq, q)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
compute_privexp :: proc "contextless" (sk: ^Private_Key, p_minus_one, q_minus_one: []u32, e: u32, tmp: []u32) -> u32 {
|
||||
// Compute phi = (p-1)*(q-1). The mulacc function sets the announced
|
||||
// bit length of t to be the sum of the announced bit lengths of
|
||||
// p-1 and q-1, which is usually exact but may overshoot by one 1
|
||||
// bit in some cases; we readjust it to its true length.
|
||||
phi := tmp
|
||||
bigint.i31_zero(phi, p_minus_one[0])
|
||||
bigint.i31_mulacc(phi, p_minus_one, q_minus_one)
|
||||
_len := (phi[0] + 31) >> 5
|
||||
phi[0] = bigint.i31_bit_length(phi[1:1+_len])
|
||||
_len = (phi[0] + 31) >> 5
|
||||
|
||||
// Divide phi by public exponent e. The final remainder r must be
|
||||
// non-zero (otherwise, the key is invalid). The quotient is k,
|
||||
// which we write over phi, since we don't need phi after that.
|
||||
r: u32
|
||||
for u := _len; u >= 1; u -= 1 {
|
||||
// Upon entry, r < e, and phi[u] < 2^31; hence,
|
||||
// hi:lo < e*2^31. Thus, the produced word k[u]
|
||||
// must be lower than 2^31, and the new remainder r
|
||||
// is lower than e.
|
||||
hi := r >> 1
|
||||
lo := (r << 31) + phi[u]
|
||||
phi[u], r = bigint.div_rem_u32(hi, lo, e)
|
||||
}
|
||||
if r == 0 {
|
||||
return 0
|
||||
}
|
||||
k := phi
|
||||
|
||||
// Compute u and v such that u*e - v*r = GCD(e,r). We use
|
||||
// a binary GCD algorithm, with 6 extra integers a, b,
|
||||
// u0, u1, v0 and v1. Initial values are:
|
||||
// a = e u0 = 1 v0 = 0
|
||||
// b = r u1 = r v1 = e-1
|
||||
// The following invariants are maintained:
|
||||
// a = u0*e - v0*r
|
||||
// b = u1*e - v1*r
|
||||
// 0 < a <= e
|
||||
// 0 < b <= r
|
||||
// 0 <= u0 <= r
|
||||
// 0 <= v0 <= e
|
||||
// 0 <= u1 <= r
|
||||
// 0 <= v1 <= e
|
||||
//
|
||||
// At each iteration, we reduce either a or b by one bit, and
|
||||
// adjust u0, u1, v0 and v1 to maintain the invariants:
|
||||
// - if a is even, then a <- a/2
|
||||
// - otherwise, if b is even, then b <- b/2
|
||||
// - otherwise, if a > b, then a <- (a-b)/2
|
||||
// - otherwise, if b > a, then b <- (b-a)/2
|
||||
// Algorithm stops when a = b. At that point, the common value
|
||||
// is the GCD of e and r; it must be 1 (otherwise, the private
|
||||
// key or public exponent is not valid). The (u0,v0) or (u1,v1)
|
||||
// pairs are the solution we are looking for.
|
||||
//
|
||||
// Since either a or b is reduced by at least 1 bit at each
|
||||
// iteration, 62 iterations are enough to reach the end
|
||||
// condition.
|
||||
//
|
||||
// To maintain the invariants, we must compute the same operations
|
||||
// on the u* and v* values that we do on a and b:
|
||||
// - When a is divided by 2, u0 and v0 must be divided by 2.
|
||||
// - When b is divided by 2, u1 and v1 must be divided by 2.
|
||||
// - When b is subtracted from a, u1 and v1 are subtracted from
|
||||
// u0 and v0, respectively.
|
||||
// - When a is subtracted from b, u0 and v0 are subtracted from
|
||||
// u1 and v1, respectively.
|
||||
//
|
||||
// However, we want to keep the u* and v* values in their proper
|
||||
// ranges. The following remarks apply:
|
||||
//
|
||||
// - When a is divided by 2, then a is even. Therefore:
|
||||
//
|
||||
// * If r is odd, then u0 and v0 must have the same parity;
|
||||
// if they are both odd, then adding r to u0 and e to v0
|
||||
// makes them both even, and the division by 2 brings them
|
||||
// back to the proper range.
|
||||
//
|
||||
// * If r is even, then u0 must be even; if v0 is odd, then
|
||||
// adding r to u0 and e to v0 makes them both even, and the
|
||||
// division by 2 brings them back to the proper range.
|
||||
//
|
||||
// Thus, all we need to do is to look at the parity of v0,
|
||||
// and add (r,e) to (u0,v0) when v0 is odd. In order to avoid
|
||||
// a 32-bit overflow, we can add ((r+1)/2,(e/2)+1) after the
|
||||
// division (r+1 does not overflow since r < e; and (e/2)+1
|
||||
// is equal to (e+1)/2 since e is odd).
|
||||
//
|
||||
// - When we subtract b from a, three cases may occur:
|
||||
//
|
||||
// * u1 <= u0 and v1 <= v0: just do the subtractions
|
||||
//
|
||||
// * u1 > u0 and v1 > v0: compute:
|
||||
// (u0, v0) <- (u0 + r - u1, v0 + e - v1)
|
||||
//
|
||||
// * u1 <= u0 and v1 > v0: compute:
|
||||
// (u0, v0) <- (u0 + r - u1, v0 + e - v1)
|
||||
//
|
||||
// The fourth case (u1 > u0 and v1 <= v0) is not possible
|
||||
// because it would contradict "b < a" (which is the reason
|
||||
// why we subtract b from a).
|
||||
//
|
||||
// The tricky case is the third one: from the equations, it
|
||||
// seems that u0 may go out of range. However, the invariants
|
||||
// and ranges of other values imply that, in that case, the
|
||||
// new u0 does not actually exceed the range.
|
||||
//
|
||||
// We can thus handle the subtraction by adding (r,e) based
|
||||
// solely on the comparison between v0 and v1.
|
||||
a, b: u32 = e, r
|
||||
u0, v0: u32 = 1, 0
|
||||
u1, v1: u32 = r, e - 1
|
||||
hr, he := (r + 1) >> 1, (e >> 1) + 1
|
||||
for _ in 0..<62 {
|
||||
oa := a & 1 // 1 if a is odd
|
||||
ob := b & 1 // 1 if b is odd
|
||||
agtb := subtle.gt(a, b) // 1 if a > b
|
||||
bgta := subtle.gt(b, a) // 1 if b > a
|
||||
|
||||
sab := oa & ob & agtb // 1 if a <- a-b
|
||||
sba := oa & ob & bgta // 1 if b <- b-a
|
||||
|
||||
// a <- a-b, u0 <- u0-u1, v0 <- v0-v1
|
||||
ctl := subtle.gt(v1, v0)
|
||||
a -= b & -sab
|
||||
u0 -= (u1 - (r & -ctl)) & -sab
|
||||
v0 -= (v1 - (e & -ctl)) & -sab
|
||||
|
||||
// b <- b-a, u1 <- u1-u0 mod r, v1 <- v1-v0 mod e
|
||||
ctl = subtle.gt(v0, v1)
|
||||
b -= a & -sba
|
||||
u1 -= (u0 - (r & -ctl)) & -sba
|
||||
v1 -= (v0 - (e & -ctl)) & -sba
|
||||
|
||||
da := subtle.not(oa) | sab // 1 if a <- a/2
|
||||
db := (oa & subtle.not(ob)) | sba // 1 if b <- b/2
|
||||
|
||||
// a <- a/2, u0 <- u0/2, v0 <- v0/2
|
||||
ctl = v0 & 1
|
||||
a ~= (a ~ (a >> 1)) & -da
|
||||
u0 ~= (u0 ~ ((u0 >> 1) + (hr & -ctl))) & -da
|
||||
v0 ~= (v0 ~ ((v0 >> 1) + (he & -ctl))) & -da
|
||||
|
||||
// b <- b/2, u1 <- u1/2 mod r, v1 <- v1/2 mod e
|
||||
ctl = v1 & 1
|
||||
b ~= (b ~ (b >> 1)) & -db
|
||||
u1 ~= (u1 ~ ((u1 >> 1) + (hr & -ctl))) & -db
|
||||
v1 ~= (v1 ~ ((v1 >> 1) + (he & -ctl))) & -db
|
||||
}
|
||||
|
||||
// Check that the GCD is indeed 1. If not, then the key is invalid
|
||||
// (and there's no harm in leaking that piece of information).
|
||||
if (a != 1) {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Now we have u0*e - v0*r = 1. Let's compute the result as:
|
||||
// d = u0 + v0*k
|
||||
// We still have k in the tmp[] array, and its announced bit
|
||||
// length is that of phi.
|
||||
m := k[1+_len:]
|
||||
m[0] = (1 << 5) + 1 // bit length is 32 bits, encoded
|
||||
m[1] = v0 & bigint.I31_MASK
|
||||
m[2] = v0 >> 31
|
||||
z := m[3:]
|
||||
bigint.i31_zero(z, k[0])
|
||||
z[1] = u0 & bigint.I31_MASK
|
||||
z[2] = u0 >> 31
|
||||
bigint.i31_mulacc(z, k, m)
|
||||
|
||||
// Encode the result.
|
||||
bigint.i31_encode(modulus_bytes(&sk._d), z)
|
||||
|
||||
return 1
|
||||
}
|
||||
49
core/crypto/rsa/rsa_mgf1.odin
Normal file
49
core/crypto/rsa/rsa_mgf1.odin
Normal file
@@ -0,0 +1,49 @@
|
||||
package rsa
|
||||
|
||||
// Copyright (c) 2018 Thomas Pornin <pornin@bolet.org>
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
|
||||
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import "core:crypto/hash"
|
||||
import "core:encoding/endian"
|
||||
|
||||
@(private)
|
||||
mgf1_xor :: proc(data: []byte, hash_algo: hash.Algorithm, seed: []byte) {
|
||||
tmp: [hash.MAX_DIGEST_SIZE]byte = ---
|
||||
ctx: hash.Context = ---
|
||||
|
||||
buf, blen := data, len(data)
|
||||
hlen := hash.DIGEST_SIZES[hash_algo]
|
||||
digest := tmp[:hlen]
|
||||
for u, c := int(0), u32(0); u < blen; u, c = u + hlen, c + 1 {
|
||||
hash.init(&ctx, hash_algo)
|
||||
hash.update(&ctx, seed)
|
||||
endian.unchecked_put_u32be(tmp[:], c)
|
||||
hash.update(&ctx, tmp[:4])
|
||||
hash.final(&ctx, digest)
|
||||
for v in 0..<hlen {
|
||||
if u + v >= blen {
|
||||
break
|
||||
}
|
||||
buf[u + v] ~= digest[v]
|
||||
}
|
||||
}
|
||||
}
|
||||
165
core/crypto/rsa/rsa_modpow_priv.odin
Normal file
165
core/crypto/rsa/rsa_modpow_priv.odin
Normal file
@@ -0,0 +1,165 @@
|
||||
package rsa
|
||||
|
||||
// Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
|
||||
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import "core:crypto"
|
||||
import bigint "core:crypto/_bigint"
|
||||
import "core:slice"
|
||||
|
||||
@(private, require_results)
|
||||
private_modpow :: proc(x: []byte, sk: ^Private_Key) -> u32 {
|
||||
U :: (2 + ((FACTOR_MAX_SIZE + 30) / 31))
|
||||
TLEN :: (4 * U) // TLEN is counted in 64-bit words
|
||||
|
||||
ensure(sk._is_initialized, "crypto/rsa: uninitialized private key")
|
||||
|
||||
// Compute the actual lengths of p and q, in bytes.
|
||||
// These lengths are not considered secret (we cannot really hide
|
||||
// them anyway in constant-time code).
|
||||
//
|
||||
// Note/yawning: The factors should already be the correct size,
|
||||
// with leading `0x00`s stripped.
|
||||
p := factor_bytes(&sk._p)
|
||||
plen := len(p)
|
||||
for plen > 0 && p[0] == 0 {
|
||||
p = p[1:]
|
||||
plen -= 1
|
||||
}
|
||||
q := factor_bytes(&sk._q)
|
||||
qlen := len(q)
|
||||
for qlen > 0 && q[0] == 0 {
|
||||
q = q[1:]
|
||||
qlen -= 1
|
||||
}
|
||||
|
||||
// Compute the maximum factor length, in 31-bit words.
|
||||
z := max(plen, qlen) << 3
|
||||
fwlen := 1
|
||||
for z > 0 {
|
||||
z -= 31
|
||||
fwlen += 1
|
||||
}
|
||||
|
||||
// Convert size to 62-bit words.
|
||||
fwlen = (fwlen + 1) >> 1
|
||||
|
||||
// We need to fit at least 6 values in the stack buffer.
|
||||
if 6 * fwlen > TLEN {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Compute signature length (in bytes).
|
||||
xlen := modulus_len(&sk._pub_key._n)
|
||||
|
||||
tmp_: [TLEN]u64 // WARNING: This must be zeroed out.
|
||||
defer crypto.zero_explicit(&tmp_, size_of(tmp_))
|
||||
tmp := tmp_[:]
|
||||
|
||||
// Decode q.
|
||||
mq := slice.reinterpret([]u32, tmp)
|
||||
bigint.i31_decode(mq, q)
|
||||
|
||||
// Decode p.
|
||||
t1 := slice.reinterpret([]u32, tmp[fwlen:])
|
||||
bigint.i31_decode(t1, p)
|
||||
|
||||
// Upstream recomputes the public modulus n, but we can just
|
||||
// decode it as our key representation stores all PKCS#1
|
||||
// private key values,
|
||||
t2 := slice.reinterpret([]u32, tmp[2*fwlen:])
|
||||
bigint.i31_decode(t2, modulus_bytes(&sk._pub_key._n))
|
||||
|
||||
// We encode the modulus into bytes, to perform the comparison
|
||||
// with bytes. We know that the product length, in bytes, is
|
||||
// exactly xlen.
|
||||
// The comparison actually computes the carry when subtracting
|
||||
// the modulus from the source value; that carry must be 1 for
|
||||
// a value in the correct range. We keep it in r, which is our
|
||||
// accumulator for the error code.
|
||||
m_buf := slice.reinterpret([]byte, tmp[4*fwlen:])
|
||||
bigint.i31_encode(m_buf[:xlen], t2)
|
||||
u := xlen
|
||||
r: u32
|
||||
for u > 0 {
|
||||
u -= 1
|
||||
wn := u32(m_buf[u])
|
||||
wx := u32(x[u])
|
||||
r = ((wx - (wn + r)) >> 8) & 1
|
||||
}
|
||||
|
||||
// Move the decoded p to another temporary buffer.
|
||||
mp := t2
|
||||
copy(mp, t1[:2*fwlen])
|
||||
|
||||
// Compute s2 = x^dq mod q.
|
||||
q0i := bigint.i31_ninv31(mq[1])
|
||||
s2 := t1
|
||||
bigint.i31_decode_reduce(s2, x, mq)
|
||||
r &= bigint.i62_modpow_opt(s2, factor_bytes(&sk._dq), mq, q0i, tmp[3*fwlen:])
|
||||
|
||||
// Compute s1 = x^dp mod p.
|
||||
p0i := bigint.i31_ninv31(mp[1])
|
||||
s1 := slice.reinterpret([]u32, tmp[3*fwlen:])
|
||||
bigint.i31_decode_reduce(s1, x, mp)
|
||||
r &= bigint.i62_modpow_opt(s1, factor_bytes(&sk._dp), mp, p0i, tmp[4*fwlen:])
|
||||
|
||||
// Compute:
|
||||
// h = (s1 - s2)*(1/q) mod p
|
||||
// s1 is an integer modulo p, but s2 is modulo q. PKCS#1 is
|
||||
// unclear about whether p may be lower than q (some existing,
|
||||
// widely deployed implementations of RSA don't tolerate p < q),
|
||||
// but we want to support that occurrence, so we need to use the
|
||||
// reduction function.
|
||||
//
|
||||
// Since we use br_i31_decode_reduce() for iq (purportedly, the
|
||||
// inverse of q modulo p), we also tolerate improperly large
|
||||
// values for this parameter.
|
||||
t1 = slice.reinterpret([]u32, tmp[4*fwlen:])
|
||||
t2 = slice.reinterpret([]u32, tmp[5*fwlen:])
|
||||
bigint.i31_reduce(t2, s2, mp)
|
||||
_ = bigint.i31_add(s1, mp, bigint.i31_sub(s1, t2, 1))
|
||||
bigint.i31_to_monty(s1, mp)
|
||||
bigint.i31_decode_reduce(t1, factor_bytes(&sk._iq), mp)
|
||||
bigint.i31_montymul(t2, s1, t1, mp, p0i)
|
||||
|
||||
// h is now in t2. We compute the final result:
|
||||
// s = s2 + q*h
|
||||
// All these operations are non-modular.
|
||||
//
|
||||
// We need mq, s2 and t2. We use the t3 buffer as destination.
|
||||
// The buffers mp, s1 and t1 are no longer needed, so we can
|
||||
// reuse them for t3. Moreover, the first step of the computation
|
||||
// is to copy s2 into t3, after which s2 is not needed. Right
|
||||
// now, mq is in slot 0, s2 is in slot 1, and t2 is in slot 5.
|
||||
// Therefore, we have ample room for t3 by simply using s2.
|
||||
t3 := s2
|
||||
bigint.i31_mulacc(t3, mq, t2)
|
||||
|
||||
// Encode the result. Since we already checked the value of xlen,
|
||||
// we can just use it right away.
|
||||
bigint.i31_encode(x, t3)
|
||||
|
||||
// The only error conditions remaining at that point are invalid
|
||||
// values for p and q (even integers).
|
||||
return p0i & q0i & r
|
||||
}
|
||||
89
core/crypto/rsa/rsa_modpow_pub.odin
Normal file
89
core/crypto/rsa/rsa_modpow_pub.odin
Normal file
@@ -0,0 +1,89 @@
|
||||
package rsa
|
||||
|
||||
// Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
|
||||
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import bigint "core:crypto/_bigint"
|
||||
import "core:encoding/endian"
|
||||
import "core:slice"
|
||||
|
||||
@(private, require_results)
|
||||
public_modpow :: proc(x: []byte, pk: ^Public_Key) -> u32 {
|
||||
TLEN :: (2 * (2 + ((MODULUS_MAX_SIZE + 30) / 31)))
|
||||
|
||||
ensure(pk._is_initialized, "crypto/rsa: uninitialized public key")
|
||||
|
||||
// Get the actual length of the modulus, and see if it fits within
|
||||
// our stack buffer. We also check that the length of x[] is valid.
|
||||
//
|
||||
// Note/yawning: The modulus should already be the correct size,
|
||||
// with leading `0x00`s stripped.
|
||||
n := modulus_bytes(&pk._n)
|
||||
nlen := modulus_len(&pk._n)
|
||||
for nlen > 0 && n[0] == 0 {
|
||||
n = n[1:]
|
||||
nlen -= 1
|
||||
}
|
||||
if nlen == 0 || nlen > (MODULUS_MAX_SIZE >> 3) || len(x) != nlen {
|
||||
return 0
|
||||
}
|
||||
z := nlen << 3
|
||||
fwlen := 1
|
||||
for z > 0 {
|
||||
z -= 31
|
||||
fwlen += 1
|
||||
}
|
||||
// Convert fwlen to a count in 62-bit words.
|
||||
fwlen = (fwlen + 1) >> 1
|
||||
|
||||
// The modulus gets decoded into m[].
|
||||
// The value to exponentiate goes into a[].
|
||||
tmp: [TLEN]u64 // WARNING: This must be zeroed out.
|
||||
m := slice.reinterpret([]u32, tmp[:fwlen])
|
||||
a := slice.reinterpret([]u32, tmp[fwlen:2*fwlen])
|
||||
|
||||
// Decode the modulus.
|
||||
bigint.i31_decode(m, n)
|
||||
m0i := bigint.i31_ninv31(m[1])
|
||||
|
||||
// Note: if m[] is even, then m0i == 0. Otherwise, m0i must be
|
||||
// an odd integer.
|
||||
r := m0i & 1
|
||||
|
||||
// Decode x[] into a[]; we also check that its value is proper.
|
||||
r &= bigint.i31_decode_mod(a, x, m)
|
||||
|
||||
// Compute the modular exponentiation.
|
||||
e_: [EXPONENT_MAX_SIZE >> 3]byte
|
||||
e_off: int
|
||||
endian.unchecked_put_u32be(e_[:], pk._e)
|
||||
if e_[0] == 0 {
|
||||
// `e = 65537` is the most common and sensible value, so this
|
||||
// is the most sensible value.
|
||||
e_off = 1
|
||||
}
|
||||
bigint.i62_modpow_opt(a, e_[e_off:], m, m0i, tmp[2*fwlen:])
|
||||
|
||||
// Encode the result.
|
||||
bigint.i31_encode(x, a)
|
||||
return r
|
||||
}
|
||||
233
core/crypto/rsa/rsa_sig_pkcs1.odin
Normal file
233
core/crypto/rsa/rsa_sig_pkcs1.odin
Normal file
@@ -0,0 +1,233 @@
|
||||
package rsa
|
||||
|
||||
// Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
|
||||
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import "core:bytes"
|
||||
import "core:crypto"
|
||||
import "core:crypto/hash"
|
||||
|
||||
// PKCS1_HASH_OIDS maps common hash algorithms to the OIDs for
|
||||
// use with PKCS#1 signatures.
|
||||
@(rodata)
|
||||
PKCS1_HASH_OIDS := #partial [hash.Algorithm][]byte {
|
||||
// WARNING: Legacy verification ONLY.
|
||||
.Insecure_SHA1 = []byte{
|
||||
0x05, 0x2B, 0x0E, 0x03, 0x02, 0x1A,
|
||||
},
|
||||
.SHA224 = []byte{
|
||||
0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04,
|
||||
},
|
||||
.SHA256 = []byte{
|
||||
0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01,
|
||||
},
|
||||
.SHA384 = []byte{
|
||||
0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02,
|
||||
},
|
||||
.SHA512 = []byte{
|
||||
0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03,
|
||||
},
|
||||
.SHA512_256 = []byte{
|
||||
0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x06,
|
||||
},
|
||||
}
|
||||
|
||||
@(private="file", rodata)
|
||||
PKCS1_SELFTEST_DIGEST_SHA256 := []byte{
|
||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
|
||||
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
|
||||
0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
|
||||
}
|
||||
|
||||
// verify_pkcs1 returns true if and only if (⟺) sig is a valid PKCS#1
|
||||
// signature by pub_key over msg, hased using hash_algo. If pre_hashed
|
||||
// is set to true, it is assumed that msg is already hashed.
|
||||
@(require_results)
|
||||
verify_pkcs1 :: proc(pub_key: ^Public_Key, hash_algo: hash.Algorithm, msg, sig: []byte, is_prehashed := false) -> bool {
|
||||
if !pub_key._is_initialized {
|
||||
return false
|
||||
}
|
||||
if len(sig) != modulus_len(&pub_key._n) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Lookup the OID.
|
||||
oid := PKCS1_HASH_OIDS[hash_algo]
|
||||
if oid == nil {
|
||||
return false
|
||||
}
|
||||
hash_len := hash.DIGEST_SIZES[hash_algo]
|
||||
|
||||
// Compute the message hash.
|
||||
msg_hash_buf: [hash.MAX_DIGEST_SIZE]byte = ---
|
||||
msg_hash: []byte
|
||||
switch is_prehashed {
|
||||
case true:
|
||||
if len(msg) != hash_len {
|
||||
return false
|
||||
}
|
||||
msg_hash = msg
|
||||
case false:
|
||||
msg_hash = hash.hash_bytes_to_buffer(hash_algo, msg, msg_hash_buf[:])
|
||||
}
|
||||
|
||||
// PKCS #1 V2.2 (RFC 8017) 8.2.2 specifies this as computing
|
||||
// and comparing the padded hash, with unpadding and extracting
|
||||
// the hash being an alternative. Upstream BearSSL implements
|
||||
// the latter, which is not a problem if done correctly (which
|
||||
// it does), however we will opt to go for implementing this
|
||||
// as specified as it is more robust against implementation
|
||||
// errors.
|
||||
|
||||
// Compute the expected hash.
|
||||
sig_buf, padded_hash_buf: [MODULUS_MAX_SIZE >> 3]byte = ---, ---
|
||||
if len(sig) > len(sig_buf) {
|
||||
return false
|
||||
}
|
||||
padded_hash_ := padded_hash_buf[:len(sig)]
|
||||
if pkcs1_sig_pad(oid, msg_hash, padded_hash_) != 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Compute the signature's padded hash.
|
||||
sig_ := sig_buf[:len(sig)]
|
||||
copy(sig_, sig)
|
||||
if public_modpow(sig_, pub_key) != 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
return bytes.equal(sig_, padded_hash_)
|
||||
}
|
||||
|
||||
// sign_pkcs1 returns true if and only if (⟺) it successfully writes
|
||||
// the PKCS#1 signature by priv_key over msg, hashed using hash_algo.
|
||||
// If pre_hashed is set to true, it is assumed that msg is already hashed.
|
||||
@(require_results)
|
||||
sign_pkcs1 :: proc(priv_key: ^Private_Key, hash_algo: hash.Algorithm, msg, sig: []byte, is_prehashed := false) -> bool {
|
||||
if !priv_key._is_initialized {
|
||||
return false
|
||||
}
|
||||
if len(sig) != modulus_len(&priv_key._pub_key._n) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Lookup the OID.
|
||||
oid := PKCS1_HASH_OIDS[hash_algo]
|
||||
if oid == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Compute the message hash.
|
||||
msg_hash_buf: [hash.MAX_DIGEST_SIZE]byte = ---
|
||||
msg_hash: []byte
|
||||
switch is_prehashed {
|
||||
case true:
|
||||
if len(msg) != hash.DIGEST_SIZES[hash_algo] {
|
||||
return false
|
||||
}
|
||||
msg_hash = msg
|
||||
case false:
|
||||
msg_hash = hash.hash_bytes_to_buffer(hash_algo, msg, msg_hash_buf[:])
|
||||
}
|
||||
|
||||
if pkcs1_sig_pad(oid, msg_hash, sig) != 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
return private_modpow(sig, priv_key) == 1
|
||||
}
|
||||
|
||||
@(private="file", require_results)
|
||||
pkcs1_sig_pad :: proc "contextless" (hash_oid, hash, x: []byte) -> u32 {
|
||||
// Padded hash value has format:
|
||||
// 00 01 FF .. FF 00 30 x1 30 x2 06 x3 OID 05 00 04 x4 HASH
|
||||
//
|
||||
// with the following rules:
|
||||
//
|
||||
// -- Total length is equal to the modulus length (unsigned
|
||||
// encoding).
|
||||
//
|
||||
// -- There must be at least eight bytes of value 0xFF.
|
||||
//
|
||||
// -- x4 is equal to the hash length (hash_len).
|
||||
//
|
||||
// -- x3 is equal to the encoded OID value length (hash_oid[0]).
|
||||
//
|
||||
// -- x2 = x3 + 4.
|
||||
//
|
||||
// -- x1 = x2 + x4 + 4 = x3 + x4 + 8.
|
||||
//
|
||||
// Note: the "05 00" is optional (signatures with and without
|
||||
// that sequence exist in practice), but notes in PKCS#1 seem to
|
||||
// indicate that the presence of that sequence (specifically,
|
||||
// an ASN.1 NULL value for the hash parameters) may be slightly
|
||||
// more "standard" than the opposite.
|
||||
xlen, hash_len := len(x), len(hash)
|
||||
|
||||
// Note/yawning: The hash OID is mandatory, as is the "05 00".
|
||||
x3 := hash_oid[0]
|
||||
|
||||
// Check that there is enough room for all the elements,
|
||||
// including at least eight bytes of value 0xFF.
|
||||
if xlen < int(x3) + hash_len + 21 {
|
||||
return 0
|
||||
}
|
||||
x[0] = 0x00
|
||||
x[1] = 0x01
|
||||
u := xlen - int(x3) - hash_len - 11
|
||||
for i in 2..< u {
|
||||
x[i] = 0xff
|
||||
}
|
||||
x[u] = 0x00
|
||||
x[u + 1] = 0x30
|
||||
x[u + 2] = x3 + byte(hash_len) + 8
|
||||
x[u + 3] = 0x30
|
||||
x[u + 4] = x3 + 4
|
||||
x[u + 5] = 0x06
|
||||
copy(x[u+6:], hash_oid)
|
||||
u += int(x3) + 7
|
||||
x[u] = 0x05
|
||||
u += 1
|
||||
x[u] = 0x00
|
||||
u += 1
|
||||
x[u] = 0x04
|
||||
u += 1
|
||||
x[u] = byte(hash_len)
|
||||
u += 1
|
||||
copy(x[u:], hash)
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
@(private)
|
||||
pkcs1_sig_selftest :: proc(priv_key: ^Private_Key) -> bool {
|
||||
sig_buf: [MODULUS_MAX_SIZE >> 3]byte = ---
|
||||
defer crypto.zero_explicit(&sig_buf, size_of(sig_buf))
|
||||
|
||||
sig := sig_buf[:private_key_size(priv_key)]
|
||||
if !sign_pkcs1(priv_key, .SHA256, PKCS1_SELFTEST_DIGEST_SHA256, sig, true) {
|
||||
return false
|
||||
}
|
||||
|
||||
return verify_pkcs1(&priv_key._pub_key, .SHA256, PKCS1_SELFTEST_DIGEST_SHA256, sig, true)
|
||||
}
|
||||
293
core/crypto/rsa/rsa_sig_pss.odin
Normal file
293
core/crypto/rsa/rsa_sig_pss.odin
Normal file
@@ -0,0 +1,293 @@
|
||||
package rsa
|
||||
|
||||
// Copyright (c) 2018 Thomas Pornin <pornin@bolet.org>
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
|
||||
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import "base:intrinsics"
|
||||
import "core:crypto"
|
||||
import bigint "core:crypto/_bigint"
|
||||
import subtle "core:crypto/_subtle"
|
||||
import "core:crypto/hash"
|
||||
|
||||
// verify_pss returns true if and only if (⟺) sig is a valid PSS
|
||||
// signature by pub_key over msg, hashed using hash_algo, and MGF1
|
||||
// parameterized by mgf1_algo and salt_len. If mgf1_algo is
|
||||
// unspecified, hash_algo will be used. If pre_hashed is set
|
||||
// to true, it is assumed that msg is already hashed.
|
||||
@(require_results)
|
||||
verify_pss :: proc(
|
||||
pub_key: ^Public_Key,
|
||||
hash_algo: hash.Algorithm,
|
||||
salt_len: int,
|
||||
msg: []byte,
|
||||
sig: []byte,
|
||||
is_prehashed := false,
|
||||
mgf1_algo := hash.Algorithm.Invalid,
|
||||
) -> bool {
|
||||
if !pub_key._is_initialized {
|
||||
return false
|
||||
}
|
||||
if hash_algo == .Invalid {
|
||||
return false
|
||||
}
|
||||
mgf1_algo_ := mgf1_algo
|
||||
if mgf1_algo == .Invalid {
|
||||
mgf1_algo_ = hash_algo
|
||||
}
|
||||
if len(sig) != modulus_len(&pub_key._n) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Compute the message hash.
|
||||
msg_hash_buf: [hash.MAX_DIGEST_SIZE]byte = ---
|
||||
hash_len := hash.DIGEST_SIZES[hash_algo]
|
||||
msg_hash: []byte
|
||||
switch is_prehashed {
|
||||
case true:
|
||||
if len(msg) != hash_len {
|
||||
return false
|
||||
}
|
||||
msg_hash = msg
|
||||
case false:
|
||||
msg_hash = hash.hash_bytes_to_buffer(hash_algo, msg, msg_hash_buf[:])
|
||||
}
|
||||
|
||||
sig_buf: [MODULUS_MAX_SIZE >> 3]byte = ---
|
||||
sig_ := sig_buf[:len(sig)]
|
||||
copy(sig_, sig)
|
||||
if public_modpow(sig_, pub_key) != 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
return pss_sig_unpad(hash_algo, mgf1_algo_, msg_hash, salt_len, pub_key, sig_) == 1
|
||||
}
|
||||
|
||||
// sign_pss returns true if and only if (⟺) it successfully writes
|
||||
// the PKCS#1 signature by priv_key over msg, hashed using hash_algo, and
|
||||
// MGF1 parameterized by mgf1_algo and salt_len. If mgf1_algo is
|
||||
// unspecified, hash_algo will be used. If pre_hashed is set to true,
|
||||
// it is assumed that msg is already hashed. A reasonable choice for
|
||||
// salt_len is the digest size of hash_algo, and FIPS 140-3 mandates
|
||||
// that as the maximum permissible size.
|
||||
//
|
||||
// This routine will fail if the system entropy source is unavailable.
|
||||
@(require_results)
|
||||
sign_pss :: proc(
|
||||
priv_key: ^Private_Key,
|
||||
hash_algo: hash.Algorithm,
|
||||
salt_len: int,
|
||||
msg: []byte,
|
||||
sig: []byte,
|
||||
is_prehashed := false,
|
||||
mgf1_algo := hash.Algorithm.Invalid,
|
||||
) -> bool {
|
||||
if !priv_key._is_initialized {
|
||||
return false
|
||||
}
|
||||
if len(sig) != modulus_len(&priv_key._pub_key._n) {
|
||||
return false
|
||||
}
|
||||
if hash_algo == .Invalid {
|
||||
return false
|
||||
}
|
||||
mgf1_algo_ := mgf1_algo
|
||||
if mgf1_algo == .Invalid {
|
||||
mgf1_algo_ = hash_algo
|
||||
}
|
||||
if !crypto.HAS_RAND_BYTES && salt_len != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Compute the message hash.
|
||||
msg_hash_buf: [hash.MAX_DIGEST_SIZE]byte = ---
|
||||
hash_len := hash.DIGEST_SIZES[hash_algo]
|
||||
msg_hash: []byte
|
||||
switch is_prehashed {
|
||||
case true:
|
||||
if len(msg) != hash_len {
|
||||
return false
|
||||
}
|
||||
msg_hash = msg
|
||||
case false:
|
||||
msg_hash = hash.hash_bytes_to_buffer(hash_algo, msg, msg_hash_buf[:])
|
||||
}
|
||||
|
||||
// Work out the exact length of n in bits.
|
||||
n := modulus_bytes(&priv_key._pub_key._n)
|
||||
assert(len(n) > 0 && n[0] != 0)
|
||||
n_bitlen := int(bigint._u32_bit_length(u32(n[0]))) + (len(n) - 1) * 8
|
||||
|
||||
if pss_sig_pad(hash_algo, mgf1_algo_, msg_hash, salt_len, n_bitlen, sig) != 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
return private_modpow(sig, priv_key) == 1
|
||||
}
|
||||
|
||||
@(private="file", require_results)
|
||||
pss_sig_unpad :: proc(
|
||||
data_algo: hash.Algorithm,
|
||||
mgf1_algo: hash.Algorithm,
|
||||
digest: []byte,
|
||||
salt_len: int,
|
||||
pk: ^Public_Key,
|
||||
sig: []byte,
|
||||
) -> u32 {
|
||||
hash_len := hash.DIGEST_SIZES[data_algo]
|
||||
x := sig
|
||||
|
||||
// Value r will be set to a non-zero value is any test fails.
|
||||
r: u32
|
||||
|
||||
// The value bit length (as an integer) must be strictly less than
|
||||
// that of the modulus.
|
||||
//
|
||||
// Note/yawning: The modulus should already be the correct size,
|
||||
// with leading `0x00`s stripped.
|
||||
n := modulus_bytes(&pk._n)
|
||||
nlen := modulus_len(&pk._n)
|
||||
u: int
|
||||
for u = 0; u < nlen; u += 1 {
|
||||
if n[u] != 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if u == nlen {
|
||||
return 0
|
||||
}
|
||||
n_bitlen := bigint._u32_bit_length(u32(n[u])) + (u32(nlen - u - 1) << 3)
|
||||
n_bitlen -= 1
|
||||
if (n_bitlen & 7) == 0 {
|
||||
r |= u32(x[0])
|
||||
x = x[1:]
|
||||
} else {
|
||||
r |= u32(x[0] & (0xFF << (n_bitlen & 7)))
|
||||
}
|
||||
xlen := int((n_bitlen + 7) >> 3)
|
||||
|
||||
// Check that the modulus is large enough for the hash value
|
||||
// length combined with the intended salt length.
|
||||
if hash_len > xlen || salt_len > xlen || (hash_len + salt_len + 2) > xlen {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Check value of rightmost byte.
|
||||
r |= u32(x[xlen - 1] ~ 0xBC)
|
||||
|
||||
// Generate the mask and XOR it into the first bytes to reveal PS;
|
||||
// we must also mask out the leading bits.
|
||||
seed := x[xlen - hash_len - 1:]
|
||||
mgf1_xor(x[:xlen - hash_len - 1], mgf1_algo, seed[:hash_len])
|
||||
if (n_bitlen & 7) != 0 {
|
||||
x[0] &= 0xFF >> (8 - (n_bitlen & 7))
|
||||
}
|
||||
|
||||
// Check that all padding bytes have the expected value.
|
||||
for u = 0; u < (xlen - hash_len - salt_len - 2); u += 1 {
|
||||
r |= u32(x[u])
|
||||
}
|
||||
r |= u32(x[xlen - hash_len - salt_len - 2] ~ 0x01)
|
||||
|
||||
// Recompute H.
|
||||
salt := x[xlen - hash_len - salt_len - 1:]
|
||||
tmp: [hash.MAX_DIGEST_SIZE]byte
|
||||
h := tmp[:hash_len]
|
||||
ctx: hash.Context = ---
|
||||
hash.init(&ctx, data_algo)
|
||||
hash.update(&ctx, tmp[:8])
|
||||
hash.update(&ctx, digest)
|
||||
hash.update(&ctx, salt[:salt_len])
|
||||
hash.final(&ctx, h)
|
||||
|
||||
// Check that the recomputed H value matches the one appearing
|
||||
// in the string.
|
||||
x = x[xlen - hash_len - 1:]
|
||||
r |= subtle.eq0(u32(crypto.compare_constant_time(h, x[:hash_len])))
|
||||
|
||||
return subtle.eq0(r)
|
||||
}
|
||||
|
||||
@(private="file", require_results)
|
||||
pss_sig_pad :: proc(
|
||||
data_algo: hash.Algorithm,
|
||||
mgf1_algo: hash.Algorithm,
|
||||
digest: []byte,
|
||||
salt_len: int,
|
||||
n_bitlen_: int,
|
||||
sig: []byte,
|
||||
) -> u32 {
|
||||
x, n_bitlen := sig, n_bitlen_
|
||||
hash_len := hash.DIGEST_SIZES[data_algo]
|
||||
|
||||
// The padded string is one bit smaller than the modulus;
|
||||
// notably, if the modulus length is equal to 1 modulo 8, then
|
||||
// the padded string will be one _byte_ smaller, and the first
|
||||
// byte will be set to 0. We apply these transformations here.
|
||||
n_bitlen -= 1
|
||||
if (n_bitlen & 7) == 0 {
|
||||
x[0] = 0
|
||||
x = x[1:]
|
||||
}
|
||||
xlen := int((n_bitlen + 7) >> 3)
|
||||
|
||||
// Check that the modulus is large enough for the hash value
|
||||
// length combined with the intended salt length.
|
||||
if hash_len > xlen || salt_len > xlen || (hash_len + salt_len + 2) > xlen {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Produce a random salt.
|
||||
salt := x[xlen - hash_len - salt_len - 1:]
|
||||
salt = salt[:salt_len]
|
||||
if salt_len != 0 {
|
||||
crypto.rand_bytes(salt)
|
||||
}
|
||||
|
||||
// Compute the seed for MGF1.
|
||||
seed := x[xlen - hash_len - 1:]
|
||||
seed = seed[:hash_len]
|
||||
ctx: hash.Context = ---
|
||||
hash.init(&ctx, data_algo)
|
||||
intrinsics.mem_zero(raw_data(seed), 8)
|
||||
hash.update(&ctx, seed[:8])
|
||||
hash.update(&ctx, digest)
|
||||
hash.update(&ctx, salt)
|
||||
hash.final(&ctx, seed)
|
||||
|
||||
// Prepare string PS (padded salt). The salt is already at the
|
||||
// right place.
|
||||
intrinsics.mem_zero(raw_data(x), xlen - salt_len - hash_len - 2)
|
||||
x[xlen - salt_len - hash_len - 2] = 0x01
|
||||
|
||||
// Generate the mask and XOR it into PS.
|
||||
mgf1_xor(x[:xlen - hash_len - 1], mgf1_algo, seed)
|
||||
|
||||
// Clear the top bits to ensure the value is lower than the
|
||||
// modulus.
|
||||
x[0] &= 0xFF >> ((u32(xlen) << 3) - u32(n_bitlen))
|
||||
|
||||
// The seed (H) is already in the right place. We just set the
|
||||
// last byte.
|
||||
x[xlen - 1] = 0xBC
|
||||
|
||||
return 1
|
||||
}
|
||||
180
core/crypto/rsa/rsa_test_key.odin
Normal file
180
core/crypto/rsa/rsa_test_key.odin
Normal file
@@ -0,0 +1,180 @@
|
||||
package rsa
|
||||
|
||||
// private_key_set_insecure_test sets the private key to the
|
||||
// pregenerated INSECURE test key "testRSA2048" from RFC 9500 2.1.
|
||||
//
|
||||
// WARNING: This key MUST only be used for testing purposes.
|
||||
@(require_results)
|
||||
private_key_set_insecure_test :: proc(priv_key: ^Private_Key) -> bool {
|
||||
// RFC 9500 2.1 "testRSA2048"
|
||||
return private_key_set_bytes(
|
||||
priv_key,
|
||||
// n
|
||||
[]byte{
|
||||
0xB0, 0xF9, 0xE8, 0x19, 0x43, 0xA7, 0xAE, 0x98,
|
||||
0x92, 0xAA, 0xDE, 0x17, 0xCA, 0x7C, 0x40, 0xF8,
|
||||
0x74, 0x4F, 0xED, 0x2F, 0x81, 0x48, 0xE6, 0xC8,
|
||||
0xEA, 0xA2, 0x7B, 0x7D, 0x00, 0x15, 0x48, 0xFB,
|
||||
0x51, 0x92, 0xAB, 0x28, 0xB5, 0x6C, 0x50, 0x60,
|
||||
0xB1, 0x18, 0xCC, 0xD1, 0x31, 0xE5, 0x94, 0x87,
|
||||
0x4C, 0x6C, 0xA9, 0x89, 0xB5, 0x6C, 0x27, 0x29,
|
||||
0x6F, 0x09, 0xFB, 0x93, 0xA0, 0x34, 0xDF, 0x32,
|
||||
0xE9, 0x7C, 0x6F, 0xF0, 0x99, 0x8C, 0xFD, 0x8E,
|
||||
0x6F, 0x42, 0xDD, 0xA5, 0x8A, 0xCD, 0x1F, 0xA9,
|
||||
0x79, 0x86, 0xF1, 0x44, 0xF3, 0xD1, 0x54, 0xD6,
|
||||
0x76, 0x50, 0x17, 0x5E, 0x68, 0x54, 0xB3, 0xA9,
|
||||
0x52, 0x00, 0x3B, 0xC0, 0x68, 0x87, 0xB8, 0x45,
|
||||
0x5A, 0xC2, 0xB1, 0x9F, 0x7B, 0x2F, 0x76, 0x50,
|
||||
0x4E, 0xBC, 0x98, 0xEC, 0x94, 0x55, 0x71, 0xB0,
|
||||
0x78, 0x92, 0x15, 0x0D, 0xDC, 0x6A, 0x74, 0xCA,
|
||||
0x0F, 0xBC, 0xD3, 0x54, 0x97, 0xCE, 0x81, 0x53,
|
||||
0x4D, 0xAF, 0x94, 0x18, 0x84, 0x4B, 0x13, 0xAE,
|
||||
0xA3, 0x1F, 0x9D, 0x5A, 0x6B, 0x95, 0x57, 0xBB,
|
||||
0xDF, 0x61, 0x9E, 0xFD, 0x4E, 0x88, 0x7F, 0x2D,
|
||||
0x42, 0xB8, 0xDD, 0x8B, 0xC9, 0x87, 0xEA, 0xE1,
|
||||
0xBF, 0x89, 0xCA, 0xB8, 0x5E, 0xE2, 0x1E, 0x35,
|
||||
0x63, 0x05, 0xDF, 0x6C, 0x07, 0xA8, 0x83, 0x8E,
|
||||
0x3E, 0xF4, 0x1C, 0x59, 0x5D, 0xCC, 0xE4, 0x3D,
|
||||
0xAF, 0xC4, 0x91, 0x23, 0xEF, 0x4D, 0x8A, 0xBB,
|
||||
0xA9, 0x3D, 0x39, 0x05, 0xE4, 0x02, 0x8D, 0x7B,
|
||||
0xA9, 0x14, 0x84, 0xA2, 0x75, 0x96, 0xE0, 0x7B,
|
||||
0x4B, 0x6E, 0xD9, 0x92, 0xF0, 0x77, 0xB5, 0x24,
|
||||
0xD3, 0xDC, 0xFE, 0x7D, 0xDD, 0x55, 0x49, 0xBE,
|
||||
0x7C, 0xCE, 0x8D, 0xA0, 0x35, 0xCF, 0xA0, 0xB3,
|
||||
0xFB, 0x8F, 0x9E, 0x46, 0xF7, 0x32, 0xB2, 0xA8,
|
||||
0x6B, 0x46, 0x01, 0x65, 0xC0, 0x8F, 0x53, 0x13,
|
||||
},
|
||||
// e
|
||||
[]byte{0x01, 0x00, 0x01},
|
||||
// d
|
||||
[]byte{
|
||||
0x41, 0x18, 0x8B, 0x20, 0xCF, 0xDB, 0xDB, 0xC2,
|
||||
0xCF, 0x1F, 0xFE, 0x75, 0x2D, 0xCB, 0xAA, 0x72,
|
||||
0x39, 0x06, 0x35, 0x2E, 0x26, 0x15, 0xD4, 0x9D,
|
||||
0xCE, 0x80, 0x59, 0x7F, 0xCF, 0x0A, 0x05, 0x40,
|
||||
0x3B, 0xEF, 0x00, 0xFA, 0x06, 0x51, 0x82, 0xF7,
|
||||
0x2D, 0xEC, 0xFB, 0x59, 0x6F, 0x4B, 0x0C, 0xE8,
|
||||
0xFF, 0x59, 0x70, 0xBA, 0xF0, 0x7A, 0x89, 0xA5,
|
||||
0x19, 0xEC, 0xC8, 0x16, 0xB2, 0xF4, 0xFF, 0xAC,
|
||||
0x50, 0x69, 0xAF, 0x1B, 0x06, 0xBF, 0xEF, 0x7B,
|
||||
0xF6, 0xBC, 0xD7, 0x9E, 0x4E, 0x81, 0xC8, 0xC5,
|
||||
0xA3, 0xA7, 0xD9, 0x13, 0x0D, 0xC3, 0xCF, 0xBA,
|
||||
0xDA, 0xE5, 0xF6, 0xD2, 0x88, 0xF9, 0xAE, 0xE3,
|
||||
0xF6, 0xFF, 0x92, 0xFA, 0xE0, 0xF8, 0x1A, 0xF5,
|
||||
0x97, 0xBE, 0xC9, 0x6A, 0xE9, 0xFA, 0xB9, 0x40,
|
||||
0x2C, 0xD5, 0xFE, 0x41, 0xF7, 0x05, 0xBE, 0xBD,
|
||||
0xB4, 0x7B, 0xB7, 0x36, 0xD3, 0xFE, 0x6C, 0x5A,
|
||||
0x51, 0xE0, 0xE2, 0x07, 0x32, 0xA9, 0x7B, 0x5E,
|
||||
0x46, 0xC1, 0xCB, 0xDB, 0x26, 0xD7, 0x48, 0x54,
|
||||
0xC6, 0xB6, 0x60, 0x4A, 0xED, 0x46, 0x37, 0x35,
|
||||
0xFF, 0x90, 0x76, 0x04, 0x65, 0x57, 0xCA, 0xF9,
|
||||
0x49, 0xBF, 0x44, 0x88, 0x95, 0xC2, 0x04, 0x32,
|
||||
0xC1, 0xE0, 0x9C, 0x01, 0x4E, 0xA7, 0x56, 0x60,
|
||||
0x43, 0x4F, 0x1A, 0x0F, 0x3B, 0xE2, 0x94, 0xBA,
|
||||
0xBC, 0x5D, 0x53, 0x0E, 0x6A, 0x10, 0x21, 0x3F,
|
||||
0x53, 0xB6, 0x03, 0x75, 0xFC, 0x84, 0xA7, 0x57,
|
||||
0x3F, 0x2A, 0xF1, 0x21, 0x55, 0x84, 0xF5, 0xB4,
|
||||
0xBD, 0xA6, 0xD4, 0xE8, 0xF9, 0xE1, 0x7A, 0x78,
|
||||
0xD9, 0x7E, 0x77, 0xB8, 0x6D, 0xA4, 0xA1, 0x84,
|
||||
0x64, 0x75, 0x31, 0x8A, 0x7A, 0x10, 0xA5, 0x61,
|
||||
0x01, 0x4E, 0xFF, 0xA2, 0x3A, 0x81, 0xEC, 0x56,
|
||||
0xE9, 0xE4, 0x10, 0x9D, 0xEF, 0x8C, 0xB3, 0xF7,
|
||||
0x97, 0x22, 0x3F, 0x7D, 0x8D, 0x0D, 0x43, 0x51,
|
||||
},
|
||||
// p
|
||||
[]byte{
|
||||
0xDD, 0x10, 0x57, 0x02, 0x38, 0x2F, 0x23, 0x2B,
|
||||
0x36, 0x81, 0xF5, 0x37, 0x91, 0xE2, 0x26, 0x17,
|
||||
0xC7, 0xBF, 0x4E, 0x9A, 0xCB, 0x81, 0xED, 0x48,
|
||||
0xDA, 0xF6, 0xD6, 0x99, 0x5D, 0xA3, 0xEA, 0xB6,
|
||||
0x42, 0x83, 0x9A, 0xFF, 0x01, 0x2D, 0x2E, 0xA6,
|
||||
0x28, 0xB9, 0x0A, 0xF2, 0x79, 0xFD, 0x3E, 0x6F,
|
||||
0x7C, 0x93, 0xCD, 0x80, 0xF0, 0x72, 0xF0, 0x1F,
|
||||
0xF2, 0x44, 0x3B, 0x3E, 0xE8, 0xF2, 0x4E, 0xD4,
|
||||
0x69, 0xA7, 0x96, 0x13, 0xA4, 0x1B, 0xD2, 0x40,
|
||||
0x20, 0xF9, 0x2F, 0xD1, 0x10, 0x59, 0xBD, 0x1D,
|
||||
0x0F, 0x30, 0x1B, 0x5B, 0xA7, 0xA9, 0xD3, 0x63,
|
||||
0x7C, 0xA8, 0xD6, 0x5C, 0x1A, 0x98, 0x15, 0x41,
|
||||
0x7D, 0x8E, 0xAB, 0x73, 0x4B, 0x0B, 0x4F, 0x3A,
|
||||
0x2C, 0x66, 0x1D, 0x9A, 0x1A, 0x82, 0xF3, 0xAC,
|
||||
0x73, 0x4C, 0x40, 0x53, 0x06, 0x69, 0xAB, 0x8E,
|
||||
0x47, 0x30, 0x45, 0xA5, 0x8E, 0x65, 0x53, 0x9D,
|
||||
},
|
||||
// q
|
||||
[]byte{
|
||||
0xCC, 0xF1, 0xE5, 0xBB, 0x90, 0xC8, 0xE9, 0x78,
|
||||
0x1E, 0xA7, 0x5B, 0xEB, 0xF1, 0x0B, 0xC2, 0x52,
|
||||
0xE1, 0x1E, 0xB0, 0x23, 0xA0, 0x26, 0x0F, 0x18,
|
||||
0x87, 0x55, 0x2A, 0x56, 0x86, 0x3F, 0x4A, 0x64,
|
||||
0x21, 0xE8, 0xC6, 0x00, 0xBF, 0x52, 0x3D, 0x6C,
|
||||
0xB1, 0xB0, 0xAD, 0xBD, 0xD6, 0x5B, 0xFE, 0xE4,
|
||||
0xA8, 0x8A, 0x03, 0x7E, 0x3D, 0x1A, 0x41, 0x5E,
|
||||
0x5B, 0xB9, 0x56, 0x48, 0xDA, 0x5A, 0x0C, 0xA2,
|
||||
0x6B, 0x54, 0xF4, 0xA6, 0x39, 0x48, 0x52, 0x2C,
|
||||
0x3D, 0x5F, 0x89, 0xB9, 0x4A, 0x72, 0xEF, 0xFF,
|
||||
0x95, 0x13, 0x4D, 0x59, 0x40, 0xCE, 0x45, 0x75,
|
||||
0x8F, 0x30, 0x89, 0x80, 0x90, 0x89, 0x56, 0x58,
|
||||
0x8E, 0xEF, 0x57, 0x5B, 0x3E, 0x4B, 0xC4, 0xC3,
|
||||
0x68, 0xCF, 0xE8, 0x13, 0xEE, 0x9C, 0x25, 0x2C,
|
||||
0x2B, 0x02, 0xE0, 0xDF, 0x91, 0xF1, 0xAA, 0x01,
|
||||
0x93, 0x8D, 0x38, 0x68, 0x5D, 0x60, 0xBA, 0x6F,
|
||||
},
|
||||
// dp
|
||||
[]byte{
|
||||
0x09, 0xED, 0x54, 0xEA, 0xED, 0x98, 0xF8, 0x4C,
|
||||
0x55, 0x7B, 0x4A, 0x86, 0xBF, 0x4F, 0x57, 0x84,
|
||||
0x93, 0xDC, 0xBC, 0x6B, 0xE9, 0x1D, 0xA1, 0x89,
|
||||
0x37, 0x04, 0x04, 0xA9, 0x08, 0x72, 0x76, 0xF4,
|
||||
0xCE, 0x51, 0xD8, 0xA1, 0x00, 0xED, 0x85, 0x7D,
|
||||
0xC2, 0xB0, 0x64, 0x94, 0x74, 0xF3, 0xF1, 0x5C,
|
||||
0xD2, 0x4C, 0x54, 0xDB, 0x28, 0x71, 0x10, 0xE5,
|
||||
0x6E, 0x5C, 0xB0, 0x08, 0x68, 0x2F, 0x91, 0x68,
|
||||
0xAA, 0x81, 0xF3, 0x14, 0x58, 0xB7, 0x43, 0x1E,
|
||||
0xCC, 0x1C, 0x44, 0x90, 0x6F, 0xDA, 0x87, 0xCA,
|
||||
0x89, 0x47, 0x10, 0xC3, 0x71, 0xE9, 0x07, 0x6C,
|
||||
0x1D, 0x49, 0xFB, 0xAE, 0x51, 0x27, 0x69, 0x34,
|
||||
0xF2, 0xAD, 0x78, 0x77, 0x89, 0xF4, 0x2D, 0x0F,
|
||||
0xA0, 0xB4, 0xC9, 0x39, 0x85, 0x5D, 0x42, 0x12,
|
||||
0x09, 0x6F, 0x70, 0x28, 0x0A, 0x4E, 0xAE, 0x7C,
|
||||
0x8A, 0x27, 0xD9, 0xC8, 0xD0, 0x77, 0x2E, 0x65,
|
||||
},
|
||||
// dq
|
||||
[]byte{
|
||||
0x8C, 0xB6, 0x85, 0x7A, 0x7B, 0xD5, 0x46, 0x5F,
|
||||
0x80, 0x04, 0x7E, 0x9B, 0x87, 0xBC, 0x00, 0x27,
|
||||
0x31, 0x84, 0x05, 0x81, 0xE0, 0x62, 0x61, 0x39,
|
||||
0x01, 0x2A, 0x5B, 0x50, 0x5F, 0x0A, 0x33, 0x84,
|
||||
0x7E, 0xB7, 0xB8, 0xC3, 0x28, 0x99, 0x49, 0xAD,
|
||||
0x48, 0x6F, 0x3B, 0x4B, 0x3D, 0x53, 0x9A, 0xB5,
|
||||
0xDA, 0x76, 0x30, 0x21, 0xCB, 0xC8, 0x2C, 0x1B,
|
||||
0xA2, 0x34, 0xA5, 0x66, 0x8D, 0xED, 0x08, 0x01,
|
||||
0xB8, 0x59, 0xF3, 0x43, 0xF1, 0xCE, 0x93, 0x04,
|
||||
0xE6, 0xFA, 0xA2, 0xB0, 0x02, 0xCA, 0xD9, 0xB7,
|
||||
0x8C, 0xDE, 0x5C, 0xDC, 0x2C, 0x1F, 0xB4, 0x17,
|
||||
0x1C, 0x42, 0x42, 0x16, 0x70, 0xA6, 0xAB, 0x0F,
|
||||
0x50, 0xCC, 0x4A, 0x19, 0x4E, 0xB3, 0x6D, 0x1C,
|
||||
0x91, 0xE9, 0x35, 0xBA, 0x01, 0xB9, 0x59, 0xD8,
|
||||
0x72, 0x8B, 0x9E, 0x64, 0x42, 0x6B, 0x3F, 0xC3,
|
||||
0xA7, 0x50, 0x6D, 0xEB, 0x52, 0x39, 0xA8, 0xA7,
|
||||
},
|
||||
// iq (aka u)
|
||||
[]byte{
|
||||
0x0A, 0x81, 0xD8, 0xA6, 0x18, 0x31, 0x4A, 0x80,
|
||||
0x3A, 0xF6, 0x1C, 0x06, 0x71, 0x1F, 0x2C, 0x39,
|
||||
0xB2, 0x66, 0xFF, 0x41, 0x4D, 0x53, 0x47, 0x6D,
|
||||
0x1D, 0xA5, 0x2A, 0x43, 0x18, 0xAA, 0xFE, 0x4B,
|
||||
0x96, 0xF0, 0xDA, 0x07, 0x15, 0x5F, 0x8A, 0x51,
|
||||
0x34, 0xDA, 0xB8, 0x8E, 0xE2, 0x9E, 0x81, 0x68,
|
||||
0x07, 0x6F, 0xCD, 0x78, 0xCA, 0x79, 0x1A, 0xC6,
|
||||
0x34, 0x42, 0xA8, 0x1C, 0xD0, 0x69, 0x39, 0x27,
|
||||
0xD8, 0x08, 0xE3, 0x35, 0xE8, 0xD8, 0xCB, 0xF2,
|
||||
0x12, 0x19, 0x07, 0x50, 0x9A, 0x57, 0x75, 0x9B,
|
||||
0x4F, 0x9A, 0x18, 0xFA, 0x3A, 0x7B, 0x33, 0x37,
|
||||
0x79, 0xED, 0xDE, 0x7A, 0x45, 0x93, 0x84, 0xF8,
|
||||
0x44, 0x4A, 0xDA, 0xEC, 0xFF, 0xEC, 0x95, 0xFD,
|
||||
0x55, 0x2B, 0x0C, 0xFC, 0xB6, 0xC7, 0xF6, 0x92,
|
||||
0x62, 0x6D, 0xDE, 0x1E, 0xF2, 0x68, 0xA4, 0x0D,
|
||||
0x2F, 0x67, 0xB5, 0xC8, 0xAA, 0x38, 0x7F, 0xF7,
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -49,6 +49,7 @@ package all
|
||||
@(require) import "core:crypto/pbkdf2"
|
||||
@(require) import "core:crypto/poly1305"
|
||||
@(require) import "core:crypto/ristretto255"
|
||||
@(require) import "core:crypto/rsa"
|
||||
@(require) import "core:crypto/sha2"
|
||||
@(require) import "core:crypto/sha3"
|
||||
@(require) import "core:crypto/shake"
|
||||
|
||||
@@ -54,6 +54,7 @@ package all
|
||||
@(require) import "core:crypto/pbkdf2"
|
||||
@(require) import "core:crypto/poly1305"
|
||||
@(require) import "core:crypto/ristretto255"
|
||||
@(require) import "core:crypto/rsa"
|
||||
@(require) import "core:crypto/sha2"
|
||||
@(require) import "core:crypto/sha3"
|
||||
@(require) import "core:crypto/shake"
|
||||
|
||||
160
tests/benchmark/crypto/benchmark_rsa.odin
Normal file
160
tests/benchmark/crypto/benchmark_rsa.odin
Normal file
@@ -0,0 +1,160 @@
|
||||
package benchmark_core_crypto
|
||||
|
||||
import "base:runtime"
|
||||
import "core:log"
|
||||
import "core:testing"
|
||||
import "core:text/table"
|
||||
import "core:time"
|
||||
|
||||
import "core:crypto"
|
||||
import "core:crypto/rsa"
|
||||
|
||||
// RSA key generation is time consuming and high variance, so it takes
|
||||
// an unreasonable amount of time to get a semi-sensible value, so this
|
||||
// is skipped by default.
|
||||
RSA_BENCH_KEYGEN: bool : #config(ODIN_BENCHMARK_RSA_KEYGEN, false)
|
||||
|
||||
@(private = "file")
|
||||
KEYGEN_ITERS :: 100
|
||||
@(private = "file")
|
||||
SIGN_ITERS :: 5000
|
||||
@(private = "file")
|
||||
ENCRYPT_ITERS :: 5000
|
||||
|
||||
@(test)
|
||||
benchmark_crypto_rsa :: proc(t: ^testing.T) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
|
||||
tbl: table.Table
|
||||
table.init(&tbl)
|
||||
defer table.destroy(&tbl)
|
||||
|
||||
table.caption(&tbl, "RSA")
|
||||
table.aligned_header_of_values(&tbl, .Right, "Operation", "Avg. Time")
|
||||
|
||||
if RSA_BENCH_KEYGEN {
|
||||
bench_keygen_2048(&tbl)
|
||||
table.row_of_values(&tbl)
|
||||
}
|
||||
bench_pkcs1_2048(&tbl)
|
||||
table.row_of_values(&tbl)
|
||||
bench_pss_2048(&tbl)
|
||||
table.row_of_values(&tbl)
|
||||
bench_oaep_2048(&tbl)
|
||||
|
||||
log_table(&tbl)
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
bench_keygen_2048 :: proc(tbl: ^table.Table) {
|
||||
if !crypto.HAS_RAND_BYTES {
|
||||
log.warnf("rsa: keygen benchmarks skipped, no system entropy source")
|
||||
}
|
||||
|
||||
priv_key: rsa.Private_Key
|
||||
start := time.tick_now()
|
||||
for _ in 0 ..< KEYGEN_ITERS {
|
||||
ok := rsa.private_key_generate(&priv_key, 2048)
|
||||
assert(ok, "keygen should succeed")
|
||||
}
|
||||
taken := time.tick_since(start) / KEYGEN_ITERS
|
||||
|
||||
append_tbl(tbl, "Keygen/2048", taken)
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
bench_pkcs1_2048 :: proc(tbl: ^table.Table) {
|
||||
priv_key: rsa.Private_Key
|
||||
_ = rsa.private_key_set_insecure_test(&priv_key)
|
||||
|
||||
msg_bytes := transmute([]byte)(SIG_MSG)
|
||||
sig_bytes: [2048 >> 3]byte
|
||||
|
||||
start := time.tick_now()
|
||||
for _ in 0 ..< SIGN_ITERS {
|
||||
ok := rsa.sign_pkcs1(&priv_key, .SHA256, msg_bytes, sig_bytes[:])
|
||||
assert(ok, "signing should succeed")
|
||||
}
|
||||
taken := time.tick_since(start) / SIGN_ITERS
|
||||
|
||||
append_tbl(tbl, "PKCS1/2048/SHA256/sign", taken)
|
||||
|
||||
start = time.tick_now()
|
||||
for _ in 0 ..< KEYGEN_ITERS {
|
||||
ok := rsa.verify_pkcs1(&priv_key._pub_key, .SHA256, msg_bytes, sig_bytes[:])
|
||||
assert(ok, "verify should succeed")
|
||||
}
|
||||
taken = time.tick_since(start) / SIGN_ITERS
|
||||
|
||||
append_tbl(tbl, "PKCS1/2048/SHA256/verify", taken)
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
bench_pss_2048 :: proc(tbl: ^table.Table) {
|
||||
priv_key: rsa.Private_Key
|
||||
_ = rsa.private_key_set_insecure_test(&priv_key)
|
||||
|
||||
msg_bytes := transmute([]byte)(SIG_MSG)
|
||||
sig_bytes: [2048 >> 3]byte
|
||||
|
||||
start := time.tick_now()
|
||||
for _ in 0 ..< SIGN_ITERS {
|
||||
ok := rsa.sign_pss(&priv_key, .SHA256, 32, msg_bytes, sig_bytes[:])
|
||||
assert(ok, "signing should succeed")
|
||||
}
|
||||
taken := time.tick_since(start) / SIGN_ITERS
|
||||
|
||||
append_tbl(tbl, "PSS/2048/SHA256/sign", taken)
|
||||
|
||||
start = time.tick_now()
|
||||
for _ in 0 ..< KEYGEN_ITERS {
|
||||
ok := rsa.verify_pss(&priv_key._pub_key, .SHA256, 32, msg_bytes, sig_bytes[:])
|
||||
assert(ok, "verify should succeed")
|
||||
}
|
||||
taken = time.tick_since(start) / SIGN_ITERS
|
||||
|
||||
append_tbl(tbl, "PSS/2048/SHA256/verify", taken)
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
bench_oaep_2048 :: proc(tbl: ^table.Table) {
|
||||
if !crypto.HAS_RAND_BYTES {
|
||||
log.info("rand_bytes not supported - skipping")
|
||||
return
|
||||
}
|
||||
|
||||
priv_key: rsa.Private_Key
|
||||
_ = rsa.private_key_set_insecure_test(&priv_key)
|
||||
|
||||
msg_bytes := transmute([]byte)(SIG_MSG)
|
||||
ciphertext_bytes: [2048 >> 3]byte
|
||||
buf: [32]byte
|
||||
|
||||
start := time.tick_now()
|
||||
for _ in 0 ..< SIGN_ITERS {
|
||||
ok := rsa.encrypt_oaep(&priv_key._pub_key, .SHA256, msg_bytes, ciphertext_bytes[:])
|
||||
assert(ok, "encryption should succeed")
|
||||
}
|
||||
taken := time.tick_since(start) / ENCRYPT_ITERS
|
||||
|
||||
append_tbl(tbl, "OAEP/2048/SHA256/encrypt", taken)
|
||||
|
||||
start = time.tick_now()
|
||||
for _ in 0 ..< KEYGEN_ITERS {
|
||||
_, ok := rsa.decrypt_oaep(&priv_key, .SHA256, ciphertext_bytes[:], buf[:])
|
||||
assert(ok, "decrypt should succeed")
|
||||
}
|
||||
taken = time.tick_since(start) / ENCRYPT_ITERS
|
||||
|
||||
append_tbl(tbl, "OAEP/2048/SHA256/decrypt", taken)
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
append_tbl :: proc(tbl: ^table.Table, op_name: string, avg_time: time.Duration) {
|
||||
table.aligned_row_of_values(
|
||||
tbl,
|
||||
.Right,
|
||||
op_name,
|
||||
table.format(tbl, "%8M", avg_time),
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package test_crypto_common
|
||||
|
||||
import "core:bytes"
|
||||
import "core:encoding/base64"
|
||||
import "core:encoding/hex"
|
||||
|
||||
// Common helpers for cryptography tests.
|
||||
@@ -23,3 +24,13 @@ hexbytes_decode :: proc(x: Hex_Bytes, allocator := context.allocator) -> []byte
|
||||
return dst
|
||||
}
|
||||
|
||||
Jwk_Bytes :: string
|
||||
|
||||
jwkbytes_decode :: proc(s: Jwk_Bytes, allocator := context.allocator) -> []byte {
|
||||
dst, err := base64.decode(s, base64.DEC_URL_TABLE, allocator = allocator)
|
||||
if err != nil {
|
||||
panic("Jwk_Bytes: invalid hex encoding")
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
419
tests/core/crypto/test_core_crypto_rsa.odin
Normal file
419
tests/core/crypto/test_core_crypto_rsa.odin
Normal file
@@ -0,0 +1,419 @@
|
||||
#+build !riscv64
|
||||
package test_core_crypto
|
||||
|
||||
import "core:bytes"
|
||||
import "core:encoding/hex"
|
||||
import "core:log"
|
||||
import "core:testing"
|
||||
|
||||
import "core:crypto"
|
||||
import "core:crypto/rsa"
|
||||
|
||||
// BUG/yawning: RISC-V fails the PSS test in CI with a nonsensical
|
||||
// bounds-checking error suggesting something spooky, as the failure
|
||||
// was not reproducible on qemu whole system emulation.
|
||||
|
||||
@(private="file")
|
||||
TEST_MSG: string : "don't let them immanentize the eschaton"
|
||||
@(private="file", rodata)
|
||||
TEST_MSG_SHA256 := []byte{
|
||||
0x50, 0x95, 0x66, 0x8e, 0x7c, 0xd0, 0xd5, 0x8e,
|
||||
0x9d, 0x59, 0xf8, 0x4a, 0x1a, 0x46, 0x5a, 0x8a,
|
||||
0x2e, 0x69, 0xcc, 0xad, 0x6a, 0xca, 0x8b, 0xb7,
|
||||
0x55, 0x55, 0x15, 0x71, 0x9b, 0xc7, 0x50, 0x88,
|
||||
}
|
||||
@(private="file", rodata)
|
||||
TEST_TLS_PMS := []byte{
|
||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
|
||||
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
|
||||
0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
|
||||
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
|
||||
0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_rsa_private_key :: proc(t: ^testing.T) {
|
||||
priv_key: rsa.Private_Key
|
||||
if !testing.expectf(
|
||||
t,
|
||||
rsa.private_key_set_insecure_test(&priv_key),
|
||||
"rsa: failed to set test key",
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
if !crypto.HAS_RAND_BYTES {
|
||||
log.info("rand_bytes not supported - skipping generate")
|
||||
return
|
||||
}
|
||||
|
||||
if !testing.expectf(
|
||||
t,
|
||||
rsa.private_key_generate(&priv_key),
|
||||
"rsa: failed to generate private key",
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
log.debugf("n=0x%s", hex.encode(priv_key._pub_key._n.v[:priv_key._pub_key._n.v_len], context.temp_allocator))
|
||||
log.debugf("e=%d", priv_key._pub_key._e)
|
||||
log.debugf("d=0x%s", hex.encode(priv_key._d.v[:priv_key._d.v_len], context.temp_allocator))
|
||||
log.debugf("p=0x%s", hex.encode(priv_key._p.v[:priv_key._p.v_len], context.temp_allocator))
|
||||
log.debugf("q=0x%s", hex.encode(priv_key._q.v[:priv_key._q.v_len], context.temp_allocator))
|
||||
log.debugf("dp=0x%s", hex.encode(priv_key._dp.v[:priv_key._dp.v_len], context.temp_allocator))
|
||||
log.debugf("dq=0x%s", hex.encode(priv_key._dq.v[:priv_key._dq.v_len], context.temp_allocator))
|
||||
log.debugf("iq=0x%s", hex.encode(priv_key._iq.v[:priv_key._iq.v_len], context.temp_allocator))
|
||||
|
||||
pub_key: rsa.Public_Key
|
||||
rsa.public_key_set_priv(&pub_key, &priv_key)
|
||||
|
||||
if !testing.expectf(
|
||||
t,
|
||||
rsa.public_key_equal(&priv_key._pub_key, &pub_key),
|
||||
"rsa: failed clone public key",
|
||||
) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_rsa_sign_pkcs1 :: proc(t: ^testing.T) {
|
||||
test_msg := transmute([]byte)(TEST_MSG)
|
||||
|
||||
priv_key: rsa.Private_Key
|
||||
_ = rsa.private_key_set_insecure_test(&priv_key)
|
||||
|
||||
pub_key: rsa.Public_Key
|
||||
rsa.public_key_set_priv(&pub_key, &priv_key)
|
||||
|
||||
// Generated with Go's crypto code. Signatures are
|
||||
// deterministic.
|
||||
expected_sig := []byte{
|
||||
0x65, 0x0e, 0xa9, 0xd1, 0x66, 0xb3, 0x61, 0x4c,
|
||||
0x67, 0x8f, 0xd6, 0x9d, 0x2e, 0xc9, 0x24, 0xdd,
|
||||
0xcc, 0xa1, 0x0b, 0xdc, 0xbb, 0x35, 0x60, 0xc5,
|
||||
0x03, 0xb1, 0xa7, 0x10, 0x64, 0x53, 0x83, 0x02,
|
||||
0x7a, 0x8b, 0xc2, 0x83, 0x7f, 0xd6, 0xfc, 0xc3,
|
||||
0xe1, 0x4d, 0x33, 0x57, 0x90, 0x81, 0x52, 0xea,
|
||||
0xcd, 0x7c, 0xaa, 0xa5, 0x98, 0x59, 0x90, 0xd1,
|
||||
0x88, 0x21, 0x87, 0xc2, 0x9d, 0x51, 0x4d, 0x45,
|
||||
0x18, 0x06, 0xa2, 0xde, 0x7a, 0xc9, 0xc9, 0x1b,
|
||||
0x3d, 0x27, 0x07, 0xe4, 0xad, 0x46, 0xe8, 0x09,
|
||||
0xe1, 0xd5, 0xbd, 0x33, 0x1a, 0x9c, 0x7f, 0x3e,
|
||||
0x1b, 0x22, 0xc0, 0xfa, 0xa1, 0x30, 0xfb, 0xda,
|
||||
0x3b, 0x09, 0x9f, 0x6f, 0x44, 0x0e, 0xa8, 0x9b,
|
||||
0x65, 0x36, 0x48, 0x07, 0x95, 0xfc, 0xf7, 0x2b,
|
||||
0x61, 0xa0, 0x95, 0x36, 0x94, 0x66, 0x01, 0x90,
|
||||
0x5f, 0xf6, 0x72, 0x05, 0x64, 0x0c, 0x50, 0x8a,
|
||||
0xce, 0xfc, 0x75, 0xc7, 0x1e, 0x05, 0x62, 0xd9,
|
||||
0x8e, 0xdf, 0x85, 0x7e, 0x5d, 0x84, 0x8c, 0x0b,
|
||||
0x18, 0x88, 0x71, 0x4a, 0x94, 0x11, 0x5e, 0x16,
|
||||
0x53, 0xd0, 0xdc, 0x8e, 0x62, 0x73, 0x9d, 0x94,
|
||||
0x66, 0x1a, 0xdf, 0xf4, 0x01, 0xaa, 0xa5, 0x33,
|
||||
0xb3, 0x0c, 0x14, 0x53, 0x5d, 0xe7, 0xdb, 0x66,
|
||||
0xf0, 0x3e, 0xf0, 0x08, 0x81, 0x77, 0x40, 0x79,
|
||||
0xa5, 0xc6, 0x48, 0x68, 0xcc, 0xa2, 0xb6, 0xf3,
|
||||
0x1f, 0xf9, 0xd5, 0xb5, 0x26, 0x04, 0x97, 0xbc,
|
||||
0x93, 0xa3, 0x19, 0x29, 0x26, 0x86, 0x89, 0xf8,
|
||||
0x11, 0xe5, 0xdd, 0xbf, 0x35, 0x6b, 0x96, 0x25,
|
||||
0xa5, 0x78, 0xab, 0x02, 0x69, 0xdf, 0x54, 0x95,
|
||||
0xc5, 0x6b, 0x95, 0xcc, 0x38, 0x7b, 0x11, 0xfa,
|
||||
0x8c, 0x98, 0xa9, 0x95, 0x7e, 0x39, 0x7c, 0xf5,
|
||||
0x2f, 0xea, 0x42, 0x6d, 0xf5, 0xaa, 0xb3, 0x16,
|
||||
0x10, 0x2f, 0x29, 0xf3, 0x4a, 0xbc, 0x47, 0xfb,
|
||||
}
|
||||
|
||||
// Note: The key generate/set routines use the prehashed
|
||||
// mode.
|
||||
sig: [2048 >> 3]byte
|
||||
ok := rsa.sign_pkcs1(
|
||||
&priv_key,
|
||||
.SHA256,
|
||||
test_msg,
|
||||
sig[:],
|
||||
)
|
||||
if !testing.expectf(t, ok, "rsa/pkcs1: signing failed") {
|
||||
return
|
||||
}
|
||||
|
||||
if !testing.expectf(
|
||||
t,
|
||||
bytes.equal(sig[:], expected_sig),
|
||||
"rsa/pkcs1: signature mismatch: %x (expected %x)",
|
||||
sig[:],
|
||||
expected_sig,
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
ok = rsa.verify_pkcs1(
|
||||
&pub_key,
|
||||
.SHA256,
|
||||
test_msg,
|
||||
expected_sig,
|
||||
)
|
||||
if !testing.expectf(t, ok, "rsa/pkcs1: verify failed") {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_rsa_sign_pss :: proc(t: ^testing.T) {
|
||||
test_msg := transmute([]byte)(TEST_MSG)
|
||||
|
||||
priv_key: rsa.Private_Key
|
||||
_ = rsa.private_key_set_insecure_test(&priv_key)
|
||||
|
||||
pub_key: rsa.Public_Key
|
||||
rsa.public_key_set_priv(&pub_key, &priv_key)
|
||||
|
||||
// Generated with Go's crypto code. Signatures are
|
||||
// NOT deterministic.
|
||||
test_sig := []byte{
|
||||
0x46, 0xd3, 0x7e, 0xb2, 0x2c, 0x82, 0xe0, 0x68,
|
||||
0x80, 0x58, 0x8c, 0x07, 0x0e, 0xe9, 0x41, 0x3f,
|
||||
0xde, 0x45, 0x4a, 0xa0, 0x3f, 0xff, 0xa3, 0xa8,
|
||||
0xc2, 0x76, 0x74, 0xf0, 0xe8, 0x44, 0x07, 0x2d,
|
||||
0xdb, 0x12, 0xd5, 0x57, 0xcc, 0x28, 0x15, 0xfa,
|
||||
0xeb, 0xb6, 0x14, 0x76, 0x10, 0x3f, 0xfc, 0xba,
|
||||
0xb1, 0x6e, 0x7f, 0x65, 0x6c, 0x9b, 0x1a, 0x62,
|
||||
0x60, 0xc4, 0xfa, 0xb0, 0x03, 0x07, 0x2b, 0x6b,
|
||||
0x6a, 0x5a, 0x84, 0x10, 0xe4, 0xf5, 0x64, 0xad,
|
||||
0xfa, 0xd3, 0x5f, 0xc9, 0xf4, 0xac, 0x70, 0xde,
|
||||
0x54, 0x06, 0x14, 0x59, 0xf7, 0x60, 0x15, 0xc9,
|
||||
0xa2, 0xe2, 0x54, 0xbc, 0x79, 0xa8, 0x02, 0x72,
|
||||
0xba, 0x6a, 0x68, 0x08, 0x15, 0x6b, 0xcb, 0xc8,
|
||||
0x55, 0x83, 0x63, 0x06, 0xe4, 0x28, 0x36, 0x6b,
|
||||
0xe6, 0x15, 0x2a, 0x21, 0xc9, 0x4f, 0xc0, 0x0e,
|
||||
0x30, 0x54, 0x3b, 0xf1, 0xa4, 0x79, 0x83, 0xb9,
|
||||
0x02, 0xd9, 0x2e, 0xa1, 0x31, 0xf4, 0x5b, 0x1b,
|
||||
0xbe, 0x44, 0x17, 0xd8, 0x42, 0x6d, 0xb3, 0x38,
|
||||
0x3f, 0x23, 0x82, 0x9e, 0x76, 0x52, 0x0c, 0x5e,
|
||||
0xa0, 0xcd, 0xd1, 0xcc, 0xe2, 0x5b, 0x71, 0xb5,
|
||||
0xca, 0x28, 0x33, 0xc3, 0x03, 0x30, 0xc5, 0xa6,
|
||||
0xdc, 0x6e, 0xfd, 0xd7, 0x34, 0x0a, 0xd2, 0x30,
|
||||
0x2c, 0x80, 0x2d, 0x31, 0xea, 0xe9, 0x44, 0x2a,
|
||||
0x7e, 0x1d, 0xde, 0x71, 0xa3, 0x7e, 0xe2, 0x5e,
|
||||
0x8a, 0x91, 0x61, 0x23, 0x5b, 0x26, 0x6d, 0x3b,
|
||||
0x44, 0xfc, 0x6b, 0x36, 0x40, 0xb1, 0xdb, 0xe7,
|
||||
0xf9, 0xe7, 0x8a, 0x12, 0x7c, 0xba, 0xd8, 0x33,
|
||||
0x1b, 0xac, 0x70, 0x59, 0x24, 0x83, 0x3a, 0x8b,
|
||||
0x2a, 0x51, 0x1b, 0x97, 0xa3, 0x8e, 0x34, 0xd1,
|
||||
0xbb, 0xc1, 0x4e, 0x00, 0xab, 0x21, 0x12, 0x53,
|
||||
0xeb, 0xda, 0x36, 0x77, 0x30, 0xea, 0x82, 0x92,
|
||||
0xb5, 0xfb, 0x07, 0xb1, 0x34, 0x37, 0x78, 0x69,
|
||||
}
|
||||
|
||||
ok := rsa.verify_pss(
|
||||
&pub_key,
|
||||
.SHA256,
|
||||
32,
|
||||
test_msg,
|
||||
test_sig,
|
||||
)
|
||||
if !testing.expectf(t, ok, "rsa/pss: verify (pregenerated) failed") {
|
||||
return
|
||||
}
|
||||
|
||||
if !crypto.HAS_RAND_BYTES {
|
||||
log.info("rand_bytes not supported - skipping round trip tests")
|
||||
return
|
||||
}
|
||||
|
||||
// Just do a simple round-trip test, as the failure
|
||||
// cases are covered by wycheproof, and de-randomizing
|
||||
// PSS is an API nightmare.
|
||||
|
||||
sig: [2048 >> 3]byte
|
||||
ok = rsa.sign_pss(
|
||||
&priv_key,
|
||||
.SHA256,
|
||||
32,
|
||||
TEST_MSG_SHA256,
|
||||
sig[:],
|
||||
true,
|
||||
)
|
||||
if !testing.expectf(t, ok, "rsa/pss: signing failed") {
|
||||
return
|
||||
}
|
||||
|
||||
ok = rsa.verify_pss(
|
||||
&pub_key,
|
||||
.SHA256,
|
||||
32,
|
||||
TEST_MSG_SHA256,
|
||||
sig[:],
|
||||
true,
|
||||
)
|
||||
if !testing.expectf(t, ok, "rsa/pss: verify failed") {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_rsa_enc_dec_oaep :: proc(t: ^testing.T) {
|
||||
test_msg := transmute([]byte)(TEST_MSG)
|
||||
|
||||
priv_key: rsa.Private_Key
|
||||
_ = rsa.private_key_set_insecure_test(&priv_key)
|
||||
|
||||
pub_key: rsa.Public_Key
|
||||
rsa.public_key_set_priv(&pub_key, &priv_key)
|
||||
|
||||
// Generated with Go's crypto code. Ciphertexts are
|
||||
// NOT deterministic.
|
||||
test_ciphertext := []byte{
|
||||
0x51, 0x7e, 0x55, 0xe8, 0xf8, 0x69, 0x9e, 0x68,
|
||||
0x8e, 0x2f, 0x38, 0xec, 0x11, 0xfb, 0x5f, 0x1e,
|
||||
0xf1, 0x9b, 0x2c, 0xea, 0x8a, 0xfb, 0x13, 0x04,
|
||||
0x2a, 0xbd, 0x4f, 0x69, 0xca, 0x11, 0x31, 0x1b,
|
||||
0xb0, 0x00, 0x18, 0x69, 0x32, 0x88, 0xe3, 0x07,
|
||||
0x82, 0xe8, 0x1d, 0x34, 0x09, 0x26, 0xdf, 0x41,
|
||||
0x1c, 0xc3, 0xf1, 0x39, 0x31, 0x45, 0xb6, 0x67,
|
||||
0xa5, 0x7c, 0xa4, 0xaf, 0x48, 0x5e, 0x96, 0x26,
|
||||
0x9b, 0x78, 0x76, 0x4e, 0xd2, 0x6e, 0x53, 0xd0,
|
||||
0x51, 0xc9, 0x80, 0x71, 0xea, 0x67, 0x8a, 0x44,
|
||||
0x1e, 0xb0, 0x81, 0x2e, 0xce, 0x43, 0x9a, 0xd9,
|
||||
0x1c, 0xea, 0x5c, 0x8b, 0x94, 0x3e, 0x1e, 0x5c,
|
||||
0xb0, 0x17, 0xb9, 0x50, 0x44, 0x22, 0xc9, 0x17,
|
||||
0xd0, 0x73, 0x54, 0x2b, 0x15, 0x68, 0xe2, 0xcf,
|
||||
0xbe, 0x0b, 0xef, 0x91, 0x11, 0xfc, 0xa6, 0x78,
|
||||
0x14, 0xdd, 0x62, 0xf3, 0xba, 0x8c, 0x8d, 0x4b,
|
||||
0x7f, 0x4b, 0xfa, 0x8b, 0x9c, 0x91, 0x08, 0x9f,
|
||||
0x39, 0x47, 0x27, 0xba, 0x9a, 0xfd, 0x2a, 0xb8,
|
||||
0x1e, 0x70, 0xa3, 0x9c, 0xe1, 0x23, 0x21, 0xc5,
|
||||
0xca, 0x00, 0x2a, 0x9b, 0x23, 0x0f, 0x15, 0xe2,
|
||||
0x9a, 0x62, 0xc2, 0x20, 0xb6, 0xe8, 0x85, 0x5f,
|
||||
0x94, 0xba, 0x72, 0x06, 0x55, 0xcf, 0x5a, 0xd6,
|
||||
0xc6, 0xc0, 0x89, 0xff, 0xd3, 0x72, 0xf9, 0x34,
|
||||
0x7a, 0x12, 0xfc, 0xe3, 0x74, 0x64, 0x00, 0xfe,
|
||||
0xa1, 0x35, 0x78, 0x66, 0x56, 0x1a, 0xde, 0x6a,
|
||||
0x83, 0x6b, 0x20, 0x06, 0xe2, 0x51, 0xae, 0xc7,
|
||||
0x27, 0x44, 0x5b, 0x21, 0x4f, 0xdf, 0xf6, 0x52,
|
||||
0x8e, 0x3a, 0x84, 0x07, 0x26, 0xc8, 0xe3, 0x6a,
|
||||
0x18, 0xd4, 0x49, 0x44, 0xd8, 0x24, 0x08, 0x94,
|
||||
0xe1, 0x67, 0xde, 0x4a, 0x8e, 0x6a, 0x2a, 0x28,
|
||||
0x72, 0x0e, 0x68, 0x9c, 0x7f, 0x55, 0x13, 0x54,
|
||||
0x13, 0x32, 0xdb, 0xe7, 0x31, 0x84, 0x90, 0xaf,
|
||||
}
|
||||
|
||||
buf: [2048 >> 3]byte
|
||||
|
||||
derived_msg, ok := rsa.decrypt_oaep(
|
||||
&priv_key,
|
||||
.SHA256,
|
||||
test_ciphertext,
|
||||
buf[:],
|
||||
)
|
||||
if !testing.expectf(t, ok, "rsa/oaep: decryption (pregenerated) failed") {
|
||||
return
|
||||
}
|
||||
if !testing.expectf(
|
||||
t,
|
||||
bytes.equal(test_msg, derived_msg),
|
||||
"rsa/oaep: unexpected plaintext: %x",
|
||||
derived_msg,
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
if !crypto.HAS_RAND_BYTES {
|
||||
log.info("rand_bytes not supported - skipping")
|
||||
return
|
||||
}
|
||||
|
||||
// Just do a simple round-trip test, as the failure
|
||||
// cases are covered by wycheproof, and de-randomizing
|
||||
// OAEP is an API nightmare.
|
||||
|
||||
ok = rsa.encrypt_oaep(
|
||||
&pub_key,
|
||||
.SHA512,
|
||||
test_msg,
|
||||
buf[:],
|
||||
)
|
||||
if !testing.expectf(t, ok, "rsa/oaep: encryption failed") {
|
||||
return
|
||||
}
|
||||
|
||||
derived_msg, ok = rsa.decrypt_oaep(
|
||||
&priv_key,
|
||||
.SHA512,
|
||||
buf[:],
|
||||
buf[:],
|
||||
)
|
||||
if !testing.expectf(t, ok, "rsa/oaep: decryption failed") {
|
||||
return
|
||||
}
|
||||
if !testing.expectf(
|
||||
t,
|
||||
bytes.equal(test_msg, derived_msg),
|
||||
"rsa/oaep: unexpected plaintext: %x",
|
||||
derived_msg,
|
||||
) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_rsa_dec_pms :: proc(t: ^testing.T) {
|
||||
priv_key: rsa.Private_Key
|
||||
_ = rsa.private_key_set_insecure_test(&priv_key)
|
||||
|
||||
// Generated with Go's crypto code. Go's PKCS1v15
|
||||
// encryption output is NOT deterministic.
|
||||
test_ciphertext := []byte{
|
||||
0x0c, 0x18, 0x48, 0x23, 0x0b, 0x4a, 0xa9, 0x20,
|
||||
0xbb, 0xa6, 0x38, 0xbf, 0x25, 0xda, 0x16, 0xd7,
|
||||
0x92, 0x72, 0x3a, 0x81, 0xbc, 0xee, 0x74, 0x6b,
|
||||
0xd8, 0x55, 0x26, 0x72, 0x81, 0x70, 0x73, 0x32,
|
||||
0xd4, 0x30, 0x56, 0xe2, 0xeb, 0x1b, 0x57, 0xf3,
|
||||
0xf5, 0x7d, 0xea, 0x3e, 0x3c, 0x62, 0x35, 0x78,
|
||||
0x06, 0x88, 0x27, 0x4d, 0x6e, 0xa0, 0x45, 0x91,
|
||||
0x62, 0x8c, 0xe9, 0x93, 0xd0, 0xde, 0x85, 0x71,
|
||||
0x01, 0x24, 0x7a, 0x1a, 0x5d, 0x03, 0x49, 0x92,
|
||||
0x73, 0x5a, 0x5b, 0x1e, 0x1e, 0x3b, 0xf0, 0xf2,
|
||||
0xea, 0x04, 0x9a, 0x87, 0x32, 0x20, 0x52, 0xbd,
|
||||
0x72, 0xc8, 0xa8, 0x38, 0xd1, 0x29, 0x97, 0x87,
|
||||
0x0b, 0x76, 0xc1, 0x68, 0xe5, 0x05, 0x72, 0xb4,
|
||||
0x4d, 0xd1, 0x95, 0xb2, 0xa8, 0x19, 0x3f, 0xc3,
|
||||
0x1e, 0xee, 0x34, 0x19, 0x72, 0xac, 0x1e, 0x4b,
|
||||
0x01, 0xd7, 0x60, 0xeb, 0x27, 0xf1, 0x12, 0x7c,
|
||||
0xbc, 0x07, 0x5d, 0xf6, 0xb9, 0xac, 0xf9, 0xdb,
|
||||
0x1a, 0xaf, 0x47, 0x13, 0x22, 0x16, 0xb5, 0x05,
|
||||
0x5a, 0x9c, 0x45, 0x69, 0x0a, 0xf1, 0x36, 0x6b,
|
||||
0xab, 0x96, 0xd7, 0x7f, 0x66, 0xff, 0x16, 0x3c,
|
||||
0x29, 0xc4, 0x10, 0x03, 0xe1, 0x35, 0xcc, 0xae,
|
||||
0x71, 0x08, 0x14, 0xff, 0x57, 0x4f, 0x3d, 0x79,
|
||||
0x7a, 0xa7, 0x19, 0x2e, 0x23, 0x08, 0xad, 0xb2,
|
||||
0xe5, 0xb1, 0xe8, 0x47, 0x6d, 0xe1, 0x24, 0x4a,
|
||||
0xd8, 0x1f, 0xe4, 0x52, 0x21, 0x3e, 0xf1, 0xcd,
|
||||
0x07, 0x72, 0xa4, 0xb8, 0x06, 0x98, 0x3a, 0x17,
|
||||
0xfd, 0xca, 0x74, 0x93, 0xb1, 0x2b, 0xd8, 0x76,
|
||||
0x7c, 0x6f, 0x71, 0xfc, 0x16, 0xef, 0x99, 0xa1,
|
||||
0xf9, 0x13, 0xeb, 0xfc, 0x34, 0x90, 0xb5, 0x00,
|
||||
0xbf, 0xdc, 0x19, 0x99, 0xb4, 0x12, 0x85, 0x25,
|
||||
0x3a, 0x49, 0x70, 0x63, 0x2f, 0xfc, 0xbc, 0xca,
|
||||
0x38, 0x2f, 0x7a, 0x0e, 0x78, 0x8d, 0x7b, 0x87,
|
||||
}
|
||||
|
||||
ok := rsa.unsafe_decrypt_tls_pms(&priv_key, test_ciphertext)
|
||||
if !testing.expectf(
|
||||
t,
|
||||
ok == 1,
|
||||
"rsa/tls_pms: decryption failed: %x",
|
||||
test_ciphertext[:48],
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
if !testing.expectf(
|
||||
t,
|
||||
bytes.equal(test_ciphertext[:48], TEST_TLS_PMS),
|
||||
"rsa/tls_pms: unexpected plaintext: %x",
|
||||
test_ciphertext[:48],
|
||||
) {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,45 @@ import "core:testing"
|
||||
// - pbkdf2_hmacsha256_test.json
|
||||
// - pbkdf2_hmacsha384_test.json
|
||||
// - pbkdf2_hmacsha512_test.json
|
||||
// - crypto/rsa
|
||||
// - rsa_pkcs1_1024_sig_gen_test.json
|
||||
// - rsa_pkcs1_1536_sig_gen_test.json
|
||||
// - rsa_pkcs1_2048_sig_gen_test.json
|
||||
// - rsa_pkcs1_3072_sig_gen_test.json
|
||||
// - rsa_pkcs1_4096_sig_gen_test.json
|
||||
// - rsa_pss_2048_sha1_mgf1_20_test.json
|
||||
// - rsa_pss_2048_sha256_mgf1_0_test.json
|
||||
// - rsa_pss_2048_sha256_mgf1_32_test.json
|
||||
// - rsa_pss_2048_sha256_mgf1sha1_20_test.json
|
||||
// - rsa_pss_2048_sha384_mgf1_48_test.json
|
||||
// - rsa_pss_2048_sha512_256_mgf1_32_test.json
|
||||
// - rsa_pss_3072_sha256_mgf1_32_test.json
|
||||
// - rsa_pss_4096_sha256_mgf1_32_test.json
|
||||
// - rsa_pss_4096_sha384_mgf1_48_test.json
|
||||
// - rsa_pss_4096_sha512_mgf1_32_test.json
|
||||
// - rsa_pss_4096_sha512_mgf1_64_test.json
|
||||
// - rsa_pss_misc_test.json
|
||||
// - rsa_oaep_2048_sha1_mgf1sha1_test.json
|
||||
// - rsa_oaep_2048_sha224_mgf1sha1_test.json
|
||||
// - rsa_oaep_2048_sha224_mgf1sha224_test.json
|
||||
// - rsa_oaep_2048_sha256_mgf1sha1_test.json
|
||||
// - rsa_oaep_2048_sha256_mgf1sha256_test.json
|
||||
// - rsa_oaep_2048_sha384_mgf1sha1_test.json
|
||||
// - rsa_oaep_2048_sha384_mgf1sha384_test.json
|
||||
// - rsa_oaep_2048_sha512_224_mgf1sha1_test.json
|
||||
// - rsa_oaep_2048_sha512_mgf1sha1_test.json
|
||||
// - rsa_oaep_2048_sha512_mgf1sha512_test.json
|
||||
// - rsa_oaep_3072_sha256_mgf1sha1_test.json
|
||||
// - rsa_oaep_3072_sha256_mgf1sha256_test.json
|
||||
// - rsa_oaep_3072_sha512_256_mgf1sha1_test.json
|
||||
// - rsa_oaep_3072_sha512_256_mgf1sha512_256_test.json
|
||||
// - rsa_oaep_3072_sha512_mgf1sha1_test.json
|
||||
// - rsa_oaep_3072_sha512_mgf1sha512_test.json
|
||||
// - rsa_oaep_4096_sha256_mgf1sha1_test.json
|
||||
// - rsa_oaep_4096_sha256_mgf1sha256_test.json
|
||||
// - rsa_oaep_4096_sha512_mgf1sha1_test.json
|
||||
// - rsa_oaep_4096_sha512_mgf1sha512_test.json
|
||||
// - rsa_oaep_misc_test.json
|
||||
// - crypto/siphash
|
||||
// - siphash_1_3_test.json
|
||||
// - siphash_2_4_test.json
|
||||
|
||||
541
tests/core/crypto/wycheproof/rsa.odin
Normal file
541
tests/core/crypto/wycheproof/rsa.odin
Normal file
@@ -0,0 +1,541 @@
|
||||
package test_wycheproof
|
||||
|
||||
import "core:encoding/hex"
|
||||
import "core:fmt"
|
||||
import "core:log"
|
||||
import "core:mem"
|
||||
import "core:os"
|
||||
import "core:testing"
|
||||
|
||||
import "core:crypto/rsa"
|
||||
|
||||
import "../common"
|
||||
|
||||
@(test)
|
||||
test_rsa_pkcs1_signature :: proc(t: ^testing.T) {
|
||||
arena: mem.Arena
|
||||
arena_backing := make([]byte, ARENA_SIZE)
|
||||
defer delete(arena_backing)
|
||||
mem.arena_init(&arena, arena_backing)
|
||||
context.allocator = mem.arena_allocator(&arena)
|
||||
|
||||
log.debug("rsa/pkcs1/signatures: starting")
|
||||
|
||||
files := []string {
|
||||
"rsa_pkcs1_1024_sig_gen_test.json",
|
||||
"rsa_pkcs1_1536_sig_gen_test.json",
|
||||
"rsa_pkcs1_2048_sig_gen_test.json",
|
||||
"rsa_pkcs1_3072_sig_gen_test.json",
|
||||
"rsa_pkcs1_4096_sig_gen_test.json",
|
||||
}
|
||||
for f in files {
|
||||
mem.free_all()
|
||||
|
||||
fn, _ := os.join_path([]string{BASE_PATH, f}, context.allocator)
|
||||
|
||||
test_vectors: Test_Vectors(Rsa_Pkcs1_Sig_Test_Group)
|
||||
load_ok := load(&test_vectors, fn)
|
||||
if !testing.expectf(t, load_ok, "Unable to load {}", f) {
|
||||
continue
|
||||
}
|
||||
|
||||
testing.expectf(t, test_rsa_pkcs1_sig(t, &test_vectors), "RSA PKCS1 signature failed")
|
||||
}
|
||||
}
|
||||
|
||||
test_rsa_pkcs1_sig :: proc(t: ^testing.T, test_vectors: ^Test_Vectors(Rsa_Pkcs1_Sig_Test_Group)) -> bool {
|
||||
JWK_KTY :: "RSA"
|
||||
|
||||
params_str := fmt.aprintf("RSA-%d/PKCS1/Signature", test_vectors.test_groups[0].key_size)
|
||||
log.debugf("%s: starting", params_str)
|
||||
|
||||
num_ran, num_passed, num_failed, num_skipped: int
|
||||
for &test_group, tg_id in test_vectors.test_groups {
|
||||
hash_str := test_group.sha
|
||||
hash_alg, _ := hash_name_to_algorithm(hash_str)
|
||||
if hash_alg == .Invalid {
|
||||
log.infof("%s: unsupported hash: %s", params_str, hash_str)
|
||||
num_ran += len(test_group.tests)
|
||||
num_skipped += len(test_group.tests)
|
||||
continue
|
||||
}
|
||||
|
||||
priv_key: rsa.Private_Key
|
||||
pub_key := &priv_key._pub_key
|
||||
|
||||
ok, have_priv: bool
|
||||
if test_group.private_key_jwk.kty == JWK_KTY {
|
||||
ok = jwk_to_private_key(&test_group.private_key_jwk, &priv_key)
|
||||
have_priv = true
|
||||
} else {
|
||||
ok = rsa.public_key_set_bytes(
|
||||
pub_key,
|
||||
common.hexbytes_decode(test_group.private_key.modulus),
|
||||
common.hexbytes_decode(test_group.private_key.public_exponent),
|
||||
)
|
||||
}
|
||||
|
||||
if !testing.expectf(t, ok, "%s/%d: invalid RSA key: %v", params_str, tg_id, test_group.private_key) {
|
||||
num_ran += len(test_group.tests)
|
||||
num_failed += len(test_group.tests)
|
||||
continue
|
||||
}
|
||||
|
||||
for &test_vector in test_group.tests {
|
||||
num_ran += 1
|
||||
|
||||
if comment := test_vector.comment; comment != "" {
|
||||
log.debugf(
|
||||
"%s/%s/%d/%d: %s: %+v",
|
||||
params_str,
|
||||
hash_str,
|
||||
tg_id,
|
||||
test_vector.tc_id,
|
||||
comment,
|
||||
test_vector.flags,
|
||||
)
|
||||
} else {
|
||||
log.debugf("%s/%s/%d/%d: %+v", params_str, hash_str, tg_id, test_vector.tc_id, test_vector.flags)
|
||||
}
|
||||
|
||||
msg := common.hexbytes_decode(test_vector.msg)
|
||||
sig := common.hexbytes_decode(test_vector.sig)
|
||||
|
||||
verify_ok := rsa.verify_pkcs1(pub_key, hash_alg, msg, sig)
|
||||
if !testing.expectf(
|
||||
t,
|
||||
result_check(test_vector.result, verify_ok, false),
|
||||
"%s/%s/%d/%d: verify failed: expected %s actual %v",
|
||||
params_str,
|
||||
hash_str,
|
||||
tg_id,
|
||||
test_vector.tc_id,
|
||||
test_vector.result,
|
||||
verify_ok,
|
||||
) {
|
||||
num_failed += 1
|
||||
continue
|
||||
}
|
||||
|
||||
if have_priv && verify_ok {
|
||||
sign_ok := rsa.sign_pkcs1(&priv_key, hash_alg, msg, sig)
|
||||
if !testing.expectf(
|
||||
t,
|
||||
sign_ok,
|
||||
"%s/%s/%d/%d: sign failed",
|
||||
params_str,
|
||||
hash_str,
|
||||
tg_id,
|
||||
test_vector.tc_id,
|
||||
) {
|
||||
num_failed += 1
|
||||
continue
|
||||
}
|
||||
if !testing.expectf(
|
||||
t,
|
||||
common.hexbytes_compare(test_vector.sig, sig),
|
||||
"%s/%s/%d/%d: sign failed: expected %s actual %s",
|
||||
params_str,
|
||||
hash_str,
|
||||
tg_id,
|
||||
test_vector.tc_id,
|
||||
test_vector.sig,
|
||||
hex.encode(sig),
|
||||
) {
|
||||
num_failed += 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
num_passed += 1
|
||||
}
|
||||
}
|
||||
|
||||
assert(num_ran == test_vectors.number_of_tests)
|
||||
assert(num_passed + num_failed + num_skipped == num_ran)
|
||||
|
||||
log.infof(
|
||||
"%s: ran %d, passed %d, failed %d, skipped %d",
|
||||
params_str,
|
||||
num_ran,
|
||||
num_passed,
|
||||
num_failed,
|
||||
num_skipped,
|
||||
)
|
||||
|
||||
return num_failed == 0
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_rsa_pss_signature :: proc(t: ^testing.T) {
|
||||
arena: mem.Arena
|
||||
arena_backing := make([]byte, ARENA_SIZE)
|
||||
defer delete(arena_backing)
|
||||
mem.arena_init(&arena, arena_backing)
|
||||
context.allocator = mem.arena_allocator(&arena)
|
||||
|
||||
log.debug("rsa/pss/signatures: starting")
|
||||
|
||||
files := []string {
|
||||
"rsa_pss_2048_sha1_mgf1_20_test.json",
|
||||
"rsa_pss_2048_sha256_mgf1_0_test.json",
|
||||
"rsa_pss_2048_sha256_mgf1_32_test.json",
|
||||
"rsa_pss_2048_sha256_mgf1sha1_20_test.json",
|
||||
"rsa_pss_2048_sha384_mgf1_48_test.json",
|
||||
"rsa_pss_2048_sha512_256_mgf1_32_test.json",
|
||||
"rsa_pss_3072_sha256_mgf1_32_test.json",
|
||||
"rsa_pss_4096_sha256_mgf1_32_test.json",
|
||||
"rsa_pss_4096_sha384_mgf1_48_test.json",
|
||||
"rsa_pss_4096_sha512_mgf1_32_test.json",
|
||||
"rsa_pss_4096_sha512_mgf1_64_test.json",
|
||||
"rsa_pss_misc_test.json",
|
||||
|
||||
// These tests include the MGF1 parameters in the public key,
|
||||
// which we do not support with our existing API.
|
||||
//
|
||||
// "rsa_pss_2048_sha1_mgf1_20_params_test.json",
|
||||
// "rsa_pss_2048_sha256_mgf1_0_params_test.json",
|
||||
// "rsa_pss_2048_sha256_mgf1_32_params_test.json",
|
||||
// "rsa_pss_2048_sha512_mgf1sha256_32_params_test.json",
|
||||
// "rsa_pss_3072_sha256_mgf1_32_params_test.json",
|
||||
// "rsa_pss_4096_sha512_mgf1_32_params_test.json",
|
||||
// "rsa_pss_4096_sha512_mgf1_64_params_test.json",
|
||||
// "rsa_pss_misc_params_test.json",
|
||||
|
||||
// Unsupported hash algorithm:
|
||||
// "rsa_pss_2048_sha512_224_mgf1_28_test.json",
|
||||
}
|
||||
for f in files {
|
||||
mem.free_all()
|
||||
|
||||
fn, _ := os.join_path([]string{BASE_PATH, f}, context.allocator)
|
||||
|
||||
test_vectors: Test_Vectors(Rsa_Pss_Sig_Test_Group)
|
||||
load_ok := load(&test_vectors, fn)
|
||||
if !testing.expectf(t, load_ok, "Unable to load {}", f) {
|
||||
continue
|
||||
}
|
||||
|
||||
testing.expectf(t, test_rsa_pss_sig(t, &test_vectors), "RSA PSS signature failed")
|
||||
}
|
||||
}
|
||||
|
||||
test_rsa_pss_sig :: proc(t: ^testing.T, test_vectors: ^Test_Vectors(Rsa_Pss_Sig_Test_Group)) -> bool {
|
||||
MGF1 :: "MGF1"
|
||||
|
||||
params_str := fmt.aprintf("RSA-%d/PSS/Signature", test_vectors.test_groups[0].key_size)
|
||||
log.debugf("%s: starting", params_str)
|
||||
|
||||
num_ran, num_passed, num_failed, num_skipped: int
|
||||
for &test_group, tg_id in test_vectors.test_groups {
|
||||
if test_group.mgf != MGF1 {
|
||||
log.infof("%s: unsupported MGF: %s", params_str, test_group.mgf)
|
||||
num_ran += len(test_group.tests)
|
||||
num_skipped += len(test_group.tests)
|
||||
continue
|
||||
}
|
||||
|
||||
hash_str := test_group.sha
|
||||
hash_alg, _ := hash_name_to_algorithm(hash_str)
|
||||
if hash_alg == .Invalid {
|
||||
log.infof("%s: unsupported hash: %s", params_str, hash_str)
|
||||
num_ran += len(test_group.tests)
|
||||
num_skipped += len(test_group.tests)
|
||||
continue
|
||||
}
|
||||
|
||||
mgf_hash_str := test_group.mfg_sha
|
||||
mgf_hash_alg, _ := hash_name_to_algorithm(mgf_hash_str)
|
||||
if mgf_hash_alg == .Invalid {
|
||||
log.infof("%s: unsupported MGF hash: %s", params_str, mgf_hash_str)
|
||||
num_ran += len(test_group.tests)
|
||||
num_skipped += len(test_group.tests)
|
||||
continue
|
||||
}
|
||||
|
||||
hash_params_str := fmt.aprintf("%s/%s(%s)", hash_str, test_group.mgf, mgf_hash_str)
|
||||
|
||||
pub_key: rsa.Public_Key
|
||||
ok := rsa.public_key_set_bytes(
|
||||
&pub_key,
|
||||
common.hexbytes_decode(test_group.public_key.modulus),
|
||||
common.hexbytes_decode(test_group.public_key.public_exponent),
|
||||
)
|
||||
if !testing.expectf(t, ok, "%s/%d: invalid RSA key: %v", params_str, tg_id, test_group.public_key) {
|
||||
num_ran += len(test_group.tests)
|
||||
num_failed += len(test_group.tests)
|
||||
continue
|
||||
}
|
||||
|
||||
for &test_vector in test_group.tests {
|
||||
num_ran += 1
|
||||
|
||||
if comment := test_vector.comment; comment != "" {
|
||||
log.debugf(
|
||||
"%s/%s/%d/%d: %s: %+v",
|
||||
params_str,
|
||||
hash_params_str,
|
||||
tg_id,
|
||||
test_vector.tc_id,
|
||||
comment,
|
||||
test_vector.flags,
|
||||
)
|
||||
} else {
|
||||
log.debugf("%s/%s/%d/%d: %+v", params_str, hash_str, tg_id, test_vector.tc_id, test_vector.flags)
|
||||
}
|
||||
|
||||
msg := common.hexbytes_decode(test_vector.msg)
|
||||
sig := common.hexbytes_decode(test_vector.sig)
|
||||
|
||||
verify_ok := rsa.verify_pss(
|
||||
&pub_key,
|
||||
hash_alg,
|
||||
test_group.s_len,
|
||||
msg,
|
||||
sig,
|
||||
false,
|
||||
mgf_hash_alg,
|
||||
)
|
||||
if !testing.expectf(
|
||||
t,
|
||||
result_check(test_vector.result, verify_ok, false),
|
||||
"%s/%s/%d/%d: verify failed: expected %s actual %v",
|
||||
params_str,
|
||||
hash_params_str,
|
||||
tg_id,
|
||||
test_vector.tc_id,
|
||||
test_vector.result,
|
||||
verify_ok,
|
||||
) {
|
||||
num_failed += 1
|
||||
continue
|
||||
}
|
||||
|
||||
num_passed += 1
|
||||
}
|
||||
}
|
||||
|
||||
assert(num_ran == test_vectors.number_of_tests)
|
||||
assert(num_passed + num_failed + num_skipped == num_ran)
|
||||
|
||||
log.infof(
|
||||
"%s: ran %d, passed %d, failed %d, skipped %d",
|
||||
params_str,
|
||||
num_ran,
|
||||
num_passed,
|
||||
num_failed,
|
||||
num_skipped,
|
||||
)
|
||||
|
||||
return num_failed == 0
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_rsa_oaep_decryption :: proc(t: ^testing.T) {
|
||||
arena: mem.Arena
|
||||
arena_backing := make([]byte, ARENA_SIZE)
|
||||
defer delete(arena_backing)
|
||||
mem.arena_init(&arena, arena_backing)
|
||||
context.allocator = mem.arena_allocator(&arena)
|
||||
|
||||
log.debug("rsa/oaep/decryption: starting")
|
||||
|
||||
files := []string {
|
||||
"rsa_oaep_2048_sha1_mgf1sha1_test.json",
|
||||
"rsa_oaep_2048_sha224_mgf1sha1_test.json",
|
||||
"rsa_oaep_2048_sha224_mgf1sha224_test.json",
|
||||
"rsa_oaep_2048_sha256_mgf1sha1_test.json",
|
||||
"rsa_oaep_2048_sha256_mgf1sha256_test.json",
|
||||
"rsa_oaep_2048_sha384_mgf1sha1_test.json",
|
||||
"rsa_oaep_2048_sha384_mgf1sha384_test.json",
|
||||
"rsa_oaep_2048_sha512_224_mgf1sha1_test.json",
|
||||
"rsa_oaep_2048_sha512_mgf1sha1_test.json",
|
||||
"rsa_oaep_2048_sha512_mgf1sha512_test.json",
|
||||
"rsa_oaep_3072_sha256_mgf1sha1_test.json",
|
||||
"rsa_oaep_3072_sha256_mgf1sha256_test.json",
|
||||
"rsa_oaep_3072_sha512_256_mgf1sha1_test.json",
|
||||
"rsa_oaep_3072_sha512_256_mgf1sha512_256_test.json",
|
||||
"rsa_oaep_3072_sha512_mgf1sha1_test.json",
|
||||
"rsa_oaep_3072_sha512_mgf1sha512_test.json",
|
||||
"rsa_oaep_4096_sha256_mgf1sha1_test.json",
|
||||
"rsa_oaep_4096_sha256_mgf1sha256_test.json",
|
||||
"rsa_oaep_4096_sha512_mgf1sha1_test.json",
|
||||
"rsa_oaep_4096_sha512_mgf1sha512_test.json",
|
||||
"rsa_oaep_misc_test.json",
|
||||
|
||||
// Unsupported hash algorithm:
|
||||
// "rsa_oaep_2048_sha512_224_mgf1sha512_224_test.json",
|
||||
}
|
||||
for f in files {
|
||||
mem.free_all()
|
||||
|
||||
fn, _ := os.join_path([]string{BASE_PATH, f}, context.allocator)
|
||||
|
||||
test_vectors: Test_Vectors(Rsa_Oaep_Dec_Test_Group)
|
||||
load_ok := load(&test_vectors, fn)
|
||||
if !testing.expectf(t, load_ok, "Unable to load {}", f) {
|
||||
continue
|
||||
}
|
||||
|
||||
testing.expectf(t, test_rsa_oaep_dec(t, &test_vectors), "RSA OAEP decryption failed")
|
||||
}
|
||||
}
|
||||
|
||||
test_rsa_oaep_dec :: proc(t: ^testing.T, test_vectors: ^Test_Vectors(Rsa_Oaep_Dec_Test_Group)) -> bool {
|
||||
MGF1 :: "MGF1"
|
||||
|
||||
params_str := fmt.aprintf("RSA-%d/OAEP/Decryption", test_vectors.test_groups[0].key_size)
|
||||
log.debugf("%s: starting", params_str)
|
||||
|
||||
num_ran, num_passed, num_failed, num_skipped: int
|
||||
for &test_group, tg_id in test_vectors.test_groups {
|
||||
if test_group.key_size > rsa.MODULUS_MAX_SIZE {
|
||||
log.infof("%s: unsupported key size: %d", params_str, test_group.key_size)
|
||||
num_ran += len(test_group.tests)
|
||||
num_skipped += len(test_group.tests)
|
||||
continue
|
||||
}
|
||||
if test_group.mgf != MGF1 {
|
||||
log.infof("%s: unsupported MGF: %s", params_str, test_group.mgf)
|
||||
num_ran += len(test_group.tests)
|
||||
num_skipped += len(test_group.tests)
|
||||
continue
|
||||
}
|
||||
|
||||
hash_str := test_group.sha
|
||||
hash_alg, _ := hash_name_to_algorithm(hash_str)
|
||||
if hash_alg == .Invalid {
|
||||
log.infof("%s: unsupported hash: %s", params_str, hash_str)
|
||||
num_ran += len(test_group.tests)
|
||||
num_skipped += len(test_group.tests)
|
||||
continue
|
||||
}
|
||||
|
||||
mgf_hash_str := test_group.mfg_sha
|
||||
mgf_hash_alg, _ := hash_name_to_algorithm(mgf_hash_str)
|
||||
if mgf_hash_alg == .Invalid {
|
||||
log.infof("%s: unsupported MGF hash: %s", params_str, mgf_hash_str)
|
||||
num_ran += len(test_group.tests)
|
||||
num_skipped += len(test_group.tests)
|
||||
continue
|
||||
}
|
||||
|
||||
hash_params_str := fmt.aprintf("%s/%s(%s)", hash_str, test_group.mgf, mgf_hash_str)
|
||||
|
||||
priv_key: rsa.Private_Key
|
||||
ok := json_to_private_key(&test_group.private_key, &priv_key)
|
||||
if !testing.expectf(t, ok, "%s/%d: invalid RSA key: %v", params_str, tg_id, test_group.private_key) {
|
||||
num_ran += len(test_group.tests)
|
||||
num_failed += len(test_group.tests)
|
||||
continue
|
||||
}
|
||||
|
||||
dst: [rsa.MODULUS_MAX_SIZE >> 3]byte = ---
|
||||
for &test_vector in test_group.tests {
|
||||
num_ran += 1
|
||||
|
||||
if comment := test_vector.comment; comment != "" {
|
||||
log.debugf(
|
||||
"%s/%s/%d/%d: %s: %+v",
|
||||
params_str,
|
||||
hash_params_str,
|
||||
tg_id,
|
||||
test_vector.tc_id,
|
||||
comment,
|
||||
test_vector.flags,
|
||||
)
|
||||
} else {
|
||||
log.debugf("%s/%s/%d/%d: %+v", params_str, hash_str, tg_id, test_vector.tc_id, test_vector.flags)
|
||||
}
|
||||
|
||||
label := common.hexbytes_decode(test_vector.label)
|
||||
ct := common.hexbytes_decode(test_vector.ct)
|
||||
|
||||
pt, decrypt_ok := rsa.decrypt_oaep(
|
||||
&priv_key,
|
||||
hash_alg,
|
||||
ct,
|
||||
dst[:],
|
||||
label,
|
||||
mgf_hash_alg,
|
||||
)
|
||||
|
||||
if !testing.expectf(
|
||||
t,
|
||||
result_check(test_vector.result, decrypt_ok, false),
|
||||
"%s/%s/%d/%d: decrypt failed: expected %s actual %v",
|
||||
params_str,
|
||||
hash_params_str,
|
||||
tg_id,
|
||||
test_vector.tc_id,
|
||||
test_vector.result,
|
||||
decrypt_ok,
|
||||
) {
|
||||
num_failed += 1
|
||||
continue
|
||||
}
|
||||
if decrypt_ok {
|
||||
if !testing.expectf(
|
||||
t,
|
||||
common.hexbytes_compare(test_vector.msg, pt),
|
||||
"%s/%s/%d/%d: decrypt failed: expected %s actual %s",
|
||||
params_str,
|
||||
hash_str,
|
||||
tg_id,
|
||||
test_vector.tc_id,
|
||||
test_vector.msg,
|
||||
hex.encode(pt),
|
||||
) {
|
||||
num_failed += 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
num_passed += 1
|
||||
}
|
||||
}
|
||||
|
||||
assert(num_ran == test_vectors.number_of_tests)
|
||||
assert(num_passed + num_failed + num_skipped == num_ran)
|
||||
|
||||
log.infof(
|
||||
"%s: ran %d, passed %d, failed %d, skipped %d",
|
||||
params_str,
|
||||
num_ran,
|
||||
num_passed,
|
||||
num_failed,
|
||||
num_skipped,
|
||||
)
|
||||
|
||||
return num_failed == 0
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
jwk_to_private_key :: proc(jwk: ^Rsa_Jwk_Private_Key, priv_key: ^rsa.Private_Key) -> bool {
|
||||
return rsa.private_key_set_bytes(
|
||||
priv_key,
|
||||
common.jwkbytes_decode(jwk.n),
|
||||
common.jwkbytes_decode(jwk.e),
|
||||
common.jwkbytes_decode(jwk.d),
|
||||
common.jwkbytes_decode(jwk.p),
|
||||
common.jwkbytes_decode(jwk.q),
|
||||
common.jwkbytes_decode(jwk.dp),
|
||||
common.jwkbytes_decode(jwk.dq),
|
||||
common.jwkbytes_decode(jwk.qi),
|
||||
)
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
json_to_private_key :: proc(json: ^Rsa_Private_Key, priv_key: ^rsa.Private_Key) -> bool {
|
||||
return rsa.private_key_set_bytes(
|
||||
priv_key,
|
||||
common.hexbytes_decode(json.modulus),
|
||||
common.hexbytes_decode(json.public_exponent),
|
||||
common.hexbytes_decode(json.private_exponent),
|
||||
common.hexbytes_decode(json.prime1),
|
||||
common.hexbytes_decode(json.prime2),
|
||||
common.hexbytes_decode(json.exponent1),
|
||||
common.hexbytes_decode(json.exponent2),
|
||||
common.hexbytes_decode(json.coefficient),
|
||||
)
|
||||
}
|
||||
@@ -150,10 +150,10 @@ Eddsa_Key :: struct {
|
||||
}
|
||||
|
||||
Eddsa_Jwk :: struct {
|
||||
kid: string `json:"kid"`,
|
||||
crv: string `json:"crv"`,
|
||||
kty: string `json:"kty"`,
|
||||
x: string `json:"x"`,
|
||||
kid: string `json:"kid"`,
|
||||
crv: string `json:"crv"`,
|
||||
kty: string `json:"kty"`,
|
||||
x: common.Jwk_Bytes `json:"x"`,
|
||||
}
|
||||
|
||||
Ecdsa_Key :: struct {
|
||||
@@ -241,3 +241,96 @@ Mldsa_Test_Vector :: struct {
|
||||
result: Result `json:"result"`,
|
||||
flags: []string `json:"flags"`,
|
||||
}
|
||||
|
||||
Rsa_Pkcs1_Sig_Test_Group :: struct {
|
||||
private_key: Rsa_Private_Key `json:"privateKey"`,
|
||||
key_asn: common.Hex_Bytes `json:"keyAsn"`,
|
||||
key_der: common.Hex_Bytes `json:"keyDer"`,
|
||||
key_pem: string `json:"keyPem"`,
|
||||
key_size: int `json:"keySize"`,
|
||||
private_key_jwk: Rsa_Jwk_Private_Key `json:"privateKeyJwk"`,
|
||||
private_key_pem: string `json:"privateKeyPem"`,
|
||||
private_key_pkcs8: common.Hex_Bytes `json:"privateKeyPkcs8"`,
|
||||
sha: string `json:"sha"`,
|
||||
type: string `json:"type"`,
|
||||
source: Test_Group_Source `json:"source"`,
|
||||
tests: []Rsa_Sig_Test_Vector `json:"tests"`,
|
||||
}
|
||||
|
||||
Rsa_Sig_Test_Vector :: struct {
|
||||
tc_id: int `json:"tcId"`,
|
||||
comment: string `json:"comment"`,
|
||||
msg: common.Hex_Bytes `json:"msg"`,
|
||||
sig: common.Hex_Bytes `json:"sig"`,
|
||||
result: Result `json:"result"`,
|
||||
flags: []string `json:"flags"`,
|
||||
}
|
||||
|
||||
Rsa_Pss_Sig_Test_Group :: struct {
|
||||
public_key: Rsa_Public_Key `json:"publicKey"`,
|
||||
public_key_asn: common.Hex_Bytes `json:"publicKeyAsn"`,
|
||||
public_key_der: common.Hex_Bytes `json:"publicKeyDer"`,
|
||||
public_key_pem: string `json:"publicKeyPem"`,
|
||||
key_size: int `json:"keySize"`,
|
||||
sha: string `json:"sha"`,
|
||||
mgf: string `json:"mgf"`,
|
||||
mfg_sha: string `json:"mgfSha"`,
|
||||
s_len: int `json:"sLen"`,
|
||||
type: string `json:"type"`,
|
||||
source: Test_Group_Source `json:"source"`,
|
||||
tests: []Rsa_Sig_Test_Vector `json:"tests"`,
|
||||
}
|
||||
|
||||
Rsa_Oaep_Dec_Test_Group :: struct {
|
||||
key_size: int `json:"keySize"`,
|
||||
private_key: Rsa_Private_Key `json:"privateKey"`,
|
||||
private_key_jwk: Rsa_Jwk_Private_Key `json:"privateKeyJwk"`,
|
||||
private_key_pem: string `json:"privateKeyPem"`,
|
||||
private_key_pkcs8: common.Hex_Bytes `json:"privateKeyPkcs8"`,
|
||||
sha: string `json:"sha"`,
|
||||
mgf: string `json:"mgf"`,
|
||||
mfg_sha: string `json:"mgfSha"`,
|
||||
type: string `json:"type"`,
|
||||
source: Test_Group_Source `json:"source"`,
|
||||
tests: []Rsa_Oaep_Test_Vector `json:"tests"`,
|
||||
}
|
||||
|
||||
Rsa_Oaep_Test_Vector :: struct {
|
||||
tc_id: int `json:"tcId"`,
|
||||
comment: string `json:"comment"`,
|
||||
msg: common.Hex_Bytes `json:"msg"`,
|
||||
ct: common.Hex_Bytes `json:"ct"`,
|
||||
label: common.Hex_Bytes `json:"label"`,
|
||||
result: Result `json:"result"`,
|
||||
flags: []string `json:"flags"`,
|
||||
}
|
||||
|
||||
Rsa_Public_Key :: struct {
|
||||
modulus: common.Hex_Bytes `json:"modulus"`,
|
||||
public_exponent: common.Hex_Bytes `json:"publicExponent"`,
|
||||
}
|
||||
|
||||
Rsa_Private_Key :: struct {
|
||||
modulus: common.Hex_Bytes `json:"modulus"`,
|
||||
private_exponent: common.Hex_Bytes `json:"privateExponent"`,
|
||||
public_exponent: common.Hex_Bytes `json:"publicExponent"`,
|
||||
prime1: common.Hex_Bytes `json:"prime1"`,
|
||||
prime2: common.Hex_Bytes `json:"prime2"`,
|
||||
exponent1: common.Hex_Bytes `json:"exponent1"`,
|
||||
exponent2: common.Hex_Bytes `json:"exponent2"`,
|
||||
coefficient: common.Hex_Bytes `json:"coefficient"`,
|
||||
}
|
||||
|
||||
Rsa_Jwk_Private_Key :: struct {
|
||||
akg: string `json:"alg"`,
|
||||
kid: string `json:"kid"`,
|
||||
kty: string `json:"kty"`,
|
||||
d: common.Jwk_Bytes `json:"d"`,
|
||||
dp: common.Jwk_Bytes `json:"dp"`,
|
||||
dq: common.Jwk_Bytes `json:"dq"`,
|
||||
e: common.Jwk_Bytes `json:"e"`,
|
||||
n: common.Jwk_Bytes `json:"n"`,
|
||||
p: common.Jwk_Bytes `json:"p"`,
|
||||
q: common.Jwk_Bytes `json:"q"`,
|
||||
qi: common.Jwk_Bytes `json:"qi"`,
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
package tests_core
|
||||
|
||||
@(require) import "crypto"
|
||||
@(require) import "crypto/bigint"
|
||||
@(require) import "hash"
|
||||
@(require) import "image"
|
||||
@(require) import "math/big"
|
||||
Reference in New Issue
Block a user