crypto/internal/fips140/rsa: add large exponent OAEP for ACVP

Similar to the prior largeexponent.go work this adds a new testing only
private key type & associated API surface for performing RSA OAEP
encryption and decryption. This is necessary to support ACVP KTS-IFC
testing with random exponents, since we can't constrain the ACVP server
selected exponent to the range supported by the production APIs.

We take the hit of duplicating some code to this test only package
instead of increasing the complexity of the production code only to
support this narrow usage.

Change-Id: I3e34385e808da4cd40dd81e8553e0a92a6c294fe
Reviewed-on: https://go-review.googlesource.com/c/go/+/769760
Reviewed-by: Roland Shoemaker <roland@golang.org>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Reviewed-by: David Chase <drchase@google.com>
This commit is contained in:
Daniel McCarney 2026-04-22 10:36:42 -04:00 committed by Filippo Valsorda
parent 7f4f2c1c7b
commit 83b29183af

View file

@ -6,10 +6,14 @@ package rsa
import (
"bytes"
"crypto/internal/constanttime"
"crypto/internal/fips140"
"crypto/internal/fips140/bigmod"
"crypto/internal/fips140/drbg"
"crypto/internal/fips140/subtle"
"errors"
"hash"
"io"
)
// TestingOnlyLargeExponentPublicKey is a variant of [PublicKey] that supports
@ -113,3 +117,249 @@ func TestingOnlyLargeExponentVerifyPSS(pub *TestingOnlyLargeExponentPublicKey, h
}
return emsaPSSVerify(digest, em, emBits, pssSaltLengthAutodetect, hash)
}
// TestingOnlyLargeExponentPrivateKey is a variant of [PrivateKey] that supports
// large public exponents. It is only meant for supporting the full ACVP test
// suite. This type must not be used in production code.
type TestingOnlyLargeExponentPrivateKey struct {
n *bigmod.Modulus
e []byte // big-endian public exponent
d *bigmod.Nat
p, q *bigmod.Modulus
dP []byte
dQ []byte
qInv *bigmod.Nat
}
func (priv *TestingOnlyLargeExponentPrivateKey) Size() int {
return (priv.n.BitLen() + 7) / 8
}
// TestingOnlyNewLargeExponentPrivateKeyWithPrecomputation creates a new RSA private key
// with a large public exponent from the given parameters. It is only meant for ACVP testing.
func TestingOnlyNewLargeExponentPrivateKeyWithPrecomputation(N []byte, e []byte, d, P, Q, dP, dQ, qInv []byte) (*TestingOnlyLargeExponentPrivateKey, error) {
n, err := bigmod.NewModulus(N)
if err != nil {
return nil, err
}
p, err := bigmod.NewModulus(P)
if err != nil {
return nil, err
}
q, err := bigmod.NewModulus(Q)
if err != nil {
return nil, err
}
dN, err := bigmod.NewNat().SetBytes(d, n)
if err != nil {
return nil, err
}
qInvNat, err := bigmod.NewNat().SetBytes(qInv, p)
if err != nil {
return nil, err
}
priv := &TestingOnlyLargeExponentPrivateKey{
n: n, e: e, d: dN, p: p, q: q,
dP: dP, dQ: dQ, qInv: qInvNat,
}
if err := checkLargeExponentPrivateKey(priv); err != nil {
return nil, err
}
return priv, nil
}
func checkLargeExponentPrivateKey(priv *TestingOnlyLargeExponentPrivateKey) error {
// Check public key portion.
pub := &TestingOnlyLargeExponentPublicKey{N: priv.n, E: priv.e}
if err := checkLargeExponentPublicKey(pub); err != nil {
return err
}
N := priv.n
p := priv.p
q := priv.q
// FIPS 186-5, Section 5.1 requires "that p and q be of the same bit length."
if p.BitLen() != q.BitLen() {
// We don't enforce this for testing, just note it.
}
// Check that pq ≡ 1 mod N (and that p < N and q < N).
pN := bigmod.NewNat().ExpandFor(N)
if _, err := pN.SetBytes(p.Nat().Bytes(p), N); err != nil {
return errors.New("crypto/rsa: invalid prime")
}
qN := bigmod.NewNat().ExpandFor(N)
if _, err := qN.SetBytes(q.Nat().Bytes(q), N); err != nil {
return errors.New("crypto/rsa: invalid prime")
}
if pN.Mul(qN, N).IsZero() != 1 {
return errors.New("crypto/rsa: p * q != n")
}
// Check that de ≡ 1 mod p-1, and de ≡ 1 mod q-1.
// Uses byte-slice exponent for large exponents.
pMinus1, err := bigmod.NewModulus(p.Nat().SubOne(p).Bytes(p))
if err != nil {
return errors.New("crypto/rsa: invalid prime")
}
dP, err := bigmod.NewNat().SetBytes(priv.dP, pMinus1)
if err != nil {
return errors.New("crypto/rsa: invalid CRT exponent")
}
de := bigmod.NewNat()
if _, err := de.SetBytes(priv.e, pMinus1); err != nil {
// Exponent might be larger than p-1, reduce it.
eNat, _ := bigmod.NewNat().SetBytes(priv.e, priv.n)
de.Mod(eNat, pMinus1)
}
de.Mul(dP, pMinus1)
if de.IsOne() != 1 {
return errors.New("crypto/rsa: invalid CRT exponent")
}
qMinus1, err := bigmod.NewModulus(q.Nat().SubOne(q).Bytes(q))
if err != nil {
return errors.New("crypto/rsa: invalid prime")
}
dQ, err := bigmod.NewNat().SetBytes(priv.dQ, qMinus1)
if err != nil {
return errors.New("crypto/rsa: invalid CRT exponent")
}
if _, err := de.SetBytes(priv.e, qMinus1); err != nil {
// Exponent might be larger than q-1, reduce it.
eNat, _ := bigmod.NewNat().SetBytes(priv.e, priv.n)
de.Mod(eNat, qMinus1)
}
de.Mul(dQ, qMinus1)
if de.IsOne() != 1 {
return errors.New("crypto/rsa: invalid CRT exponent")
}
// Check that qInv * q ≡ 1 mod p.
qP, err := bigmod.NewNat().SetOverflowingBytes(q.Nat().Bytes(q), p)
if err != nil {
// q >= 2^⌈log2(p)⌉
qP = bigmod.NewNat().Mod(q.Nat(), p)
}
if qP.Mul(priv.qInv, p).IsOne() != 1 {
return errors.New("crypto/rsa: invalid CRT coefficient")
}
return nil
}
func decryptLargeExponent(priv *TestingOnlyLargeExponentPrivateKey, ciphertext []byte) ([]byte, error) {
N := priv.n
c, err := bigmod.NewNat().SetBytes(ciphertext, N)
if err != nil {
return nil, ErrDecryption
}
// CRT-based decryption (same as regular decrypt, doesn't use E).
P, Q := priv.p, priv.q
t0 := bigmod.NewNat()
// m = c ^ Dp mod p
m := bigmod.NewNat().Exp(t0.Mod(c, P), priv.dP, P)
// m2 = c ^ Dq mod q
m2 := bigmod.NewNat().Exp(t0.Mod(c, Q), priv.dQ, Q)
// m = m - m2 mod p
m.Sub(t0.Mod(m2, P), P)
// m = m * Qinv mod p
m.Mul(priv.qInv, P)
// m = m * q mod N
m.ExpandFor(N).Mul(t0.Mod(Q.Nat(), N), N)
// m = m + m2 mod N
m.Add(m2.ExpandFor(N), N)
return m.Bytes(N), nil
}
// TestingOnlyLargeExponentDecryptOAEP decrypts ciphertext using RSAES-OAEP with
// a private key that has a large public exponent. It is only meant for ACVP testing.
func TestingOnlyLargeExponentDecryptOAEP(hash, mgfHash hash.Hash, priv *TestingOnlyLargeExponentPrivateKey, ciphertext []byte, label []byte) ([]byte, error) {
fipsSelfTest()
fips140.RecordApproved()
checkApprovedHash(hash)
k := priv.Size()
if len(ciphertext) > k || k < hash.Size()*2+2 {
return nil, ErrDecryption
}
em, err := decryptLargeExponent(priv, ciphertext)
if err != nil {
return nil, err
}
hash.Reset()
hash.Write(label)
lHash := hash.Sum(nil)
firstByteIsZero := constanttime.ByteEq(em[0], 0)
seed := em[1 : hash.Size()+1]
db := em[hash.Size()+1:]
mgf1XOR(seed, mgfHash, db)
mgf1XOR(db, mgfHash, seed)
lHash2 := db[0:hash.Size()]
lHash2Good := subtle.ConstantTimeCompare(lHash, lHash2)
var lookingForIndex, index, invalid int
lookingForIndex = 1
rest := db[hash.Size():]
for i := 0; i < len(rest); i++ {
equals0 := constanttime.ByteEq(rest[i], 0)
equals1 := constanttime.ByteEq(rest[i], 1)
index = constanttime.Select(lookingForIndex&equals1, i, index)
lookingForIndex = constanttime.Select(equals1, 0, lookingForIndex)
invalid = constanttime.Select(lookingForIndex&^equals0, 1, invalid)
}
if firstByteIsZero&lHash2Good&^invalid&^lookingForIndex != 1 {
return nil, ErrDecryption
}
return rest[index+1:], nil
}
// TestingOnlyLargeExponentEncryptOAEP encrypts the given message with RSAES-OAEP
// using a public key with a large exponent. It is only meant for ACVP testing.
func TestingOnlyLargeExponentEncryptOAEP(hash, mgfHash hash.Hash, random io.Reader, pub *TestingOnlyLargeExponentPublicKey, msg []byte, label []byte) ([]byte, error) {
fipsSelfTest()
fips140.RecordApproved()
checkApprovedHash(hash)
if err := checkLargeExponentPublicKey(pub); err != nil {
return nil, err
}
k := pub.Size()
if len(msg) > k-2*hash.Size()-2 {
return nil, ErrMessageTooLong
}
hash.Reset()
hash.Write(label)
lHash := hash.Sum(nil)
em := make([]byte, k)
seed := em[1 : 1+hash.Size()]
db := em[1+hash.Size():]
copy(db[0:hash.Size()], lHash)
db[len(db)-len(msg)-1] = 1
copy(db[len(db)-len(msg):], msg)
if err := drbg.ReadWithReader(random, seed); err != nil {
return nil, err
}
mgf1XOR(db, mgfHash, seed)
mgf1XOR(seed, mgfHash, db)
return encryptLargeExponent(pub, em)
}