core/cryto/rsa: Initial import

This commit is contained in:
Yawning Angel
2026-07-09 03:43:33 +09:00
parent 483221ef97
commit e1d32cb19f
22 changed files with 3564 additions and 4 deletions

7
core/crypto/rsa/doc.odin Normal file
View File

@@ -0,0 +1,7 @@
/*
RSA (RivestShamirAdleman) cryptosystem.
See:
- [[ https://www.rfc-editor.org/info/rfc8017/ ]]
*/
package rsa

444
core/crypto/rsa/rsa.odin Normal file
View 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)
}
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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]
}
}
}

View 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
}

View 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
}

View 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)
}

View 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
}

View 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,
},
)
}

View File

@@ -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"

View File

@@ -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"

View 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),
)
}

View File

@@ -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
}

View 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
}
}

View File

@@ -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

View 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),
)
}

View File

@@ -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"`,
}

View File

@@ -2,6 +2,7 @@
package tests_core
@(require) import "crypto"
@(require) import "crypto/bigint"
@(require) import "hash"
@(require) import "image"
@(require) import "math/big"