boulder/goodkey/good_key_test.go

421 lines
21 KiB
Go

package goodkey
import (
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"fmt"
"math/big"
"testing"
"github.com/letsencrypt/boulder/test"
)
// testingPolicy is a simple policy which allows all of the key types, so that
// the unit tests can exercise checks against all key types.
var testingPolicy = &KeyPolicy{allowedKeys: AllowedKeys{
RSA2048: true, RSA3072: true, RSA4096: true,
ECDSAP256: true, ECDSAP384: true, ECDSAP521: true,
}}
func TestUnknownKeyType(t *testing.T) {
notAKey := struct{}{}
err := testingPolicy.GoodKey(context.Background(), notAKey)
test.AssertError(t, err, "Should have rejected a key of unknown type")
test.AssertEquals(t, err.Error(), "unsupported key type struct {}")
}
func TestNilKey(t *testing.T) {
err := testingPolicy.GoodKey(context.Background(), nil)
test.AssertError(t, err, "Should have rejected a nil key")
test.AssertEquals(t, err.Error(), "unsupported key type <nil>")
}
func TestSmallModulus(t *testing.T) {
pubKey := rsa.PublicKey{
N: big.NewInt(0),
E: 65537,
}
// 2040 bits
_, ok := pubKey.N.SetString("104192126510885102608953552259747211060428328569316484779167706297543848858189721071301121307701498317286069484848193969810800653457088975832436062805901725915630417996487259956349018066196416400386483594314258078114607080545265502078791826837453107382149801328758721235866366842649389274931060463277516954884108984101391466769505088222180613883737986792254164577832157921425082478871935498631777878563742033332460445633026471887331001305450139473524438241478798689974351175769895824322173301257621327448162705637127373457350813027123239805772024171112299987923305882261194120410409098448380641378552305583392176287", 10)
if !ok {
t.Errorf("error parsing pubkey modulus")
}
err := testingPolicy.GoodKey(context.Background(), &pubKey)
test.AssertError(t, err, "Should have rejected too-short key")
test.AssertEquals(t, err.Error(), "key size not supported: 2040")
}
func TestLargeModulus(t *testing.T) {
pubKey := rsa.PublicKey{
N: big.NewInt(0),
E: 65537,
}
// 4097 bits
_, ok := pubKey.N.SetString("1528586537844618544364689295678280797814937047039447018548513699782432768815684971832418418955305671838918285565080181315448131784543332408348488544125812746629522583979538961638790013578302979210481729874191053412386396889481430969071543569003141391030053024684850548909056275565684242965892176703473950844930842702506635531145654194239072799616096020023445127233557468234181352398708456163013484600764686209741158795461806441111028922165846800488957692595308009319392149669715238691709012014980470238746838534949750493558807218940354555205690667168930634644030378921382266510932028134500172599110460167962515262077587741235811653717121760943005253103187409557573174347385738572144714188928416780963680160418832333908040737262282830643745963536624555340279793555475547508851494656512855403492456740439533790565640263514349940712999516725281940465613417922773583725174223806589481568984323871222072582132221706797917380250216291620957692131931099423995355390698925093903005385497308399692769135287821632877871068909305276870015125960884987746154344006895331078411141197233179446805991116541744285238281451294472577537413640009811940462311100056023815261650331552185459228689469446389165886801876700815724561451940764544990177661873073", 10)
if !ok {
t.Errorf("error parsing pubkey modulus")
}
err := testingPolicy.GoodKey(context.Background(), &pubKey)
test.AssertError(t, err, "Should have rejected too-long key")
test.AssertEquals(t, err.Error(), "key size not supported: 4097")
}
func TestModulusModulo8(t *testing.T) {
bigOne := big.NewInt(1)
key := rsa.PublicKey{
N: bigOne.Lsh(bigOne, 2048),
E: 5,
}
err := testingPolicy.GoodKey(context.Background(), &key)
test.AssertError(t, err, "Should have rejected modulus with length not divisible by 8")
test.AssertEquals(t, err.Error(), "key size not supported: 2049")
}
var mod2048 = big.NewInt(0).Sub(big.NewInt(0).Lsh(big.NewInt(1), 2048), big.NewInt(1))
func TestNonStandardExp(t *testing.T) {
evenMod := big.NewInt(0).Add(big.NewInt(1).Lsh(big.NewInt(1), 2047), big.NewInt(2))
key := rsa.PublicKey{
N: evenMod,
E: (1 << 16),
}
err := testingPolicy.GoodKey(context.Background(), &key)
test.AssertError(t, err, "Should have rejected non-standard exponent")
test.AssertEquals(t, err.Error(), "key exponent must be 65537")
}
func TestEvenModulus(t *testing.T) {
evenMod := big.NewInt(0).Add(big.NewInt(1).Lsh(big.NewInt(1), 2047), big.NewInt(2))
key := rsa.PublicKey{
N: evenMod,
E: (1 << 16) + 1,
}
err := testingPolicy.GoodKey(context.Background(), &key)
test.AssertError(t, err, "Should have rejected even modulus")
test.AssertEquals(t, err.Error(), "key divisible by small prime")
}
func TestModulusDivisibleBySmallPrime(t *testing.T) {
key := rsa.PublicKey{
N: mod2048,
E: (1 << 16) + 1,
}
err := testingPolicy.GoodKey(context.Background(), &key)
test.AssertError(t, err, "Should have rejected modulus divisible by 3")
test.AssertEquals(t, err.Error(), "key divisible by small prime")
}
func TestROCA(t *testing.T) {
n, ok := big.NewInt(1).SetString("19089470491547632015867380494603366846979936677899040455785311493700173635637619562546319438505971838982429681121352968394792665704951454132311441831732124044135181992768774222852895664400681270897445415599851900461316070972022018317962889565731866601557238345786316235456299813772607869009873279585912430769332375239444892105064608255089298943707214066350230292124208314161171265468111771687514518823144499250339825049199688099820304852696380797616737008621384107235756455735861506433065173933123259184114000282435500939123478591192413006994709825840573671701120771013072419520134975733578923370992644987545261926257", 10)
if !ok {
t.Fatal("failed to parse")
}
key := rsa.PublicKey{
N: n,
E: 65537,
}
err := testingPolicy.GoodKey(context.Background(), &key)
test.AssertError(t, err, "Should have rejected ROCA-weak key")
test.AssertEquals(t, err.Error(), "key generated by vulnerable Infineon-based hardware")
}
func TestGoodKey(t *testing.T) {
private, err := rsa.GenerateKey(rand.Reader, 2048)
test.AssertNotError(t, err, "Error generating key")
test.AssertNotError(t, testingPolicy.GoodKey(context.Background(), &private.PublicKey), "Should have accepted good key")
}
func TestECDSABadCurve(t *testing.T) {
for _, curve := range invalidCurves {
private, err := ecdsa.GenerateKey(curve, rand.Reader)
test.AssertNotError(t, err, "Error generating key")
err = testingPolicy.GoodKey(context.Background(), &private.PublicKey)
test.AssertError(t, err, "Should have rejected key with unsupported curve")
test.AssertEquals(t, err.Error(), fmt.Sprintf("ECDSA curve %s not allowed", curve.Params().Name))
}
}
var invalidCurves = []elliptic.Curve{
elliptic.P224(),
}
var validCurves = []elliptic.Curve{
elliptic.P256(),
elliptic.P384(),
elliptic.P521(),
}
func TestECDSAGoodKey(t *testing.T) {
for _, curve := range validCurves {
private, err := ecdsa.GenerateKey(curve, rand.Reader)
test.AssertNotError(t, err, "Error generating key")
test.AssertNotError(t, testingPolicy.GoodKey(context.Background(), &private.PublicKey), "Should have accepted good key")
}
}
func TestECDSANotOnCurveX(t *testing.T) {
for _, curve := range validCurves {
// Change a public key so that it is no longer on the curve.
private, err := ecdsa.GenerateKey(curve, rand.Reader)
test.AssertNotError(t, err, "Error generating key")
private.X.Add(private.X, big.NewInt(1))
err = testingPolicy.GoodKey(context.Background(), &private.PublicKey)
test.AssertError(t, err, "Should not have accepted key not on the curve")
test.AssertEquals(t, err.Error(), "key point is not on the curve")
}
}
func TestECDSANotOnCurveY(t *testing.T) {
for _, curve := range validCurves {
// Again with Y.
private, err := ecdsa.GenerateKey(curve, rand.Reader)
test.AssertNotError(t, err, "Error generating key")
// Change the public key so that it is no longer on the curve.
private.Y.Add(private.Y, big.NewInt(1))
err = testingPolicy.GoodKey(context.Background(), &private.PublicKey)
test.AssertError(t, err, "Should not have accepted key not on the curve")
test.AssertEquals(t, err.Error(), "key point is not on the curve")
}
}
func TestECDSANegative(t *testing.T) {
for _, curve := range validCurves {
// Check that negative X is not accepted.
private, err := ecdsa.GenerateKey(curve, rand.Reader)
test.AssertNotError(t, err, "Error generating key")
private.X.Neg(private.X)
err = testingPolicy.GoodKey(context.Background(), &private.PublicKey)
test.AssertError(t, err, "Should not have accepted key with negative X")
test.AssertEquals(t, err.Error(), "key x, y must not be negative")
// Check that negative Y is not accepted.
private.X.Neg(private.X)
private.Y.Neg(private.Y)
err = testingPolicy.GoodKey(context.Background(), &private.PublicKey)
test.AssertError(t, err, "Should not have accepted key with negative Y")
test.AssertEquals(t, err.Error(), "key x, y must not be negative")
}
}
func TestECDSAXOutsideField(t *testing.T) {
for _, curve := range validCurves {
// Check that X outside [0, p-1] is not accepted.
private, err := ecdsa.GenerateKey(curve, rand.Reader)
test.AssertNotError(t, err, "Error generating key")
private.X.Mul(private.X, private.Curve.Params().P)
err = testingPolicy.GoodKey(context.Background(), &private.PublicKey)
test.AssertError(t, err, "Should not have accepted key with a X > p-1")
test.AssertEquals(t, err.Error(), "key x, y must not exceed P-1")
}
}
func TestECDSAYOutsideField(t *testing.T) {
for _, curve := range validCurves {
// Check that Y outside [0, p-1] is not accepted.
private, err := ecdsa.GenerateKey(curve, rand.Reader)
test.AssertNotError(t, err, "Error generating key")
private.X.Mul(private.Y, private.Curve.Params().P)
err = testingPolicy.GoodKey(context.Background(), &private.PublicKey)
test.AssertError(t, err, "Should not have accepted key with a Y > p-1")
test.AssertEquals(t, err.Error(), "key x, y must not exceed P-1")
}
}
func TestECDSAIdentity(t *testing.T) {
for _, curve := range validCurves {
// The point at infinity is 0,0, it should not be accepted.
public := ecdsa.PublicKey{
Curve: curve,
X: big.NewInt(0),
Y: big.NewInt(0),
}
err := testingPolicy.GoodKey(context.Background(), &public)
test.AssertError(t, err, "Should not have accepted key with point at infinity")
test.AssertEquals(t, err.Error(), "key x, y must not be the point at infinity")
}
}
func TestNonRefKey(t *testing.T) {
private, err := rsa.GenerateKey(rand.Reader, 2048)
test.AssertNotError(t, err, "Error generating key")
test.AssertError(t, testingPolicy.GoodKey(context.Background(), private.PublicKey), "Accepted non-reference key")
}
func TestDBBlocklistAccept(t *testing.T) {
for _, testCheck := range []BlockedKeyCheckFunc{
nil,
func(context.Context, []byte) (bool, error) {
return false, nil
},
} {
policy, err := NewPolicy(nil, testCheck)
test.AssertNotError(t, err, "NewKeyPolicy failed")
k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
test.AssertNotError(t, err, "ecdsa.GenerateKey failed")
err = policy.GoodKey(context.Background(), k.Public())
test.AssertNotError(t, err, "GoodKey failed with a non-blocked key")
}
}
func TestDBBlocklistReject(t *testing.T) {
testCheck := func(context.Context, []byte) (bool, error) {
return true, nil
}
policy, err := NewPolicy(nil, testCheck)
test.AssertNotError(t, err, "NewKeyPolicy failed")
k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
test.AssertNotError(t, err, "ecdsa.GenerateKey failed")
err = policy.GoodKey(context.Background(), k.Public())
test.AssertError(t, err, "GoodKey didn't fail with a blocked key")
test.AssertErrorIs(t, err, ErrBadKey)
test.AssertEquals(t, err.Error(), "public key is forbidden")
}
func TestDefaultAllowedKeys(t *testing.T) {
policy, err := NewPolicy(nil, nil)
test.AssertNotError(t, err, "NewPolicy with nil config failed")
test.Assert(t, policy.allowedKeys.RSA2048, "RSA 2048 should be allowed")
test.Assert(t, policy.allowedKeys.RSA3072, "RSA 3072 should be allowed")
test.Assert(t, policy.allowedKeys.RSA4096, "RSA 4096 should be allowed")
test.Assert(t, policy.allowedKeys.ECDSAP256, "NIST P256 should be allowed")
test.Assert(t, policy.allowedKeys.ECDSAP384, "NIST P384 should be allowed")
test.Assert(t, !policy.allowedKeys.ECDSAP521, "NIST P521 should not be allowed")
policy, err = NewPolicy(&Config{}, nil)
test.AssertNotError(t, err, "NewPolicy with nil config.AllowedKeys failed")
test.Assert(t, policy.allowedKeys.RSA2048, "RSA 2048 should be allowed")
test.Assert(t, policy.allowedKeys.RSA3072, "RSA 3072 should be allowed")
test.Assert(t, policy.allowedKeys.RSA4096, "RSA 4096 should be allowed")
test.Assert(t, policy.allowedKeys.ECDSAP256, "NIST P256 should be allowed")
test.Assert(t, policy.allowedKeys.ECDSAP384, "NIST P384 should be allowed")
test.Assert(t, !policy.allowedKeys.ECDSAP521, "NIST P521 should not be allowed")
}
func TestRSAStrangeSize(t *testing.T) {
k := &rsa.PublicKey{N: big.NewInt(10)}
err := testingPolicy.GoodKey(context.Background(), k)
test.AssertError(t, err, "expected GoodKey to fail")
test.AssertEquals(t, err.Error(), "key size not supported: 4")
}
func TestCheckPrimeFactorsTooClose(t *testing.T) {
type testCase struct {
name string
p string
q string
expectRounds int
}
testCases := []testCase{
{
// The factors 59 and 101 multiply to 5959. The values a and b calculated
// by Fermat's method will be 80 and 21. The ceil of the square root of
// 5959 is 78. Therefore it takes 3 rounds of Fermat's method to find the
// factors.
name: "tiny",
p: "101",
q: "59",
expectRounds: 3,
},
{
// These factors differ only in their second-to-last digit. They're so close
// that a single iteration of Fermat's method is sufficient to find them.
name: "very close",
p: "12451309173743450529024753538187635497858772172998414407116324997634262083672423797183640278969532658774374576700091736519352600717664126766443002156788367",
q: "12451309173743450529024753538187635497858772172998414407116324997634262083672423797183640278969532658774374576700091736519352600717664126766443002156788337",
expectRounds: 1,
},
{
// These factors differ by slightly more than 2^256, which takes fourteen
// rounds to factor.
name: "still too close",
p: "11779932606551869095289494662458707049283241949932278009554252037480401854504909149712949171865707598142483830639739537075502512627849249573564209082969463",
q: "11779932606551869095289494662458707049283241949932278009554252037480401854503793357623711855670284027157475142731886267090836872063809791989556295953329083",
expectRounds: 14,
},
{
// These factors come from a real canon printer in the wild with a broken
// key generation mechanism.
name: "canon printer (2048 bit, 1 round)",
p: "155536235030272749691472293262418471207550926406427515178205576891522284497518443889075039382254334975506248481615035474816604875321501901699955105345417152355947783063521554077194367454070647740704883461064399268622437721385112646454393005862535727615809073410746393326688230040267160616554768771412289114449",
q: "155536235030272749691472293262418471207550926406427515178205576891522284497518443889075039382254334975506248481615035474816604875321501901699955105345417152355947783063521554077194367454070647740704883461064399268622437721385112646454393005862535727615809073410746393326688230040267160616554768771412289114113",
expectRounds: 1,
},
{
// These factors come from a real innsbruck printer in the wild with a
// broken key generation mechanism.
name: "innsbruck printer (4096 bit, 1 round)",
p: "25868808535211632564072019392873831934145242707953960515208595626279836366691068618582894100813803673421320899654654938470888358089618966238341690624345530870988951109006149164192566967552401505863871260691612081236189439839963332690997129144163260418447718577834226720411404568398865166471102885763673744513186211985402019037772108416694793355840983833695882936201196462579254234744648546792097397517107797153785052856301942321429858537224127598198913168345965493941246097657533085617002572245972336841716321849601971924830462771411171570422802773095537171762650402420866468579928479284978914972383512240254605625661",
q: "25868808535211632564072019392873831934145242707953960515208595626279836366691068618582894100813803673421320899654654938470888358089618966238341690624345530870988951109006149164192566967552401505863871260691612081236189439839963332690997129144163260418447718577834226720411404568398865166471102885763673744513186211985402019037772108416694793355840983833695882936201196462579254234744648546792097397517107797153785052856301942321429858537224127598198913168345965493941246097657533085617002572245972336841716321849601971924830462771411171570422802773095537171762650402420866468579928479284978914972383512240254605624819",
expectRounds: 1,
},
{
// FIPS requires that |p-q| > 2^(nlen/2 - 100). For example, a 2048-bit
// RSA key must have prime factors with a difference of at least 2^924.
// These two factors have a difference of exactly 2^924 + 4, just *barely*
// FIPS-compliant. Their first different digit is in column 52 of this
// file, which makes them vastly further apart than the cases above. Their
// product cannot be factored even with 100,000,000 rounds of Fermat's
// Algorithm.
name: "barely FIPS compliant (2048 bit)",
p: "151546560166767007654995655231369126386504564489055366370313539237722892921762327477057109592614214965864835328962951695621854530739049166771701397343693962526456985866167580660948398404000483264137738772983130282095332559392185543017295488346592188097443414824871619976114874896240350402349774470198190454623",
q: "151546560166767007654995655231510939369872272987323309037144546294925352276321214430320942815891873491060949332482502812040326472743233767963240491605860423063942576391584034077877871768428333113881339606298282107984376151546711223157061364850161576363709081794948857957944390170575452970542651659150041855843",
expectRounds: -1,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
p, ok := new(big.Int).SetString(tc.p, 10)
if !ok {
t.Fatalf("failed to load prime factor p (%s)", tc.p)
}
q, ok := new(big.Int).SetString(tc.q, 10)
if !ok {
t.Fatalf("failed to load prime factor q (%s)", tc.q)
}
n := new(big.Int).Mul(p, q)
err := checkPrimeFactorsTooClose(n, 100)
if tc.expectRounds > 0 {
test.AssertError(t, err, "failed to factor n")
test.AssertContains(t, err.Error(), fmt.Sprintf("p: %s", tc.p))
test.AssertContains(t, err.Error(), fmt.Sprintf("q: %s", tc.q))
test.AssertContains(t, err.Error(), fmt.Sprintf("in %d rounds", tc.expectRounds))
} else {
test.AssertNil(t, err, "factored the unfactorable")
}
})
}
}
func benchFermat(rounds int, b *testing.B) {
n := big.NewInt(0)
n.SetString("801622717394169050106926578578301725055526605503706912100006286161529273473377413824975745384114446662904851914935980611269769546695796451504160869649117000521094368058953989236438103975426680952076533198797388295193391779933559668812684470909409457778161223896975426492372231040386646816154793996920467596916193680611886097694746368434138296683172992347929528214464827172059378866098534956467670429228681248968588692628197119606249988365750115578731538804653322115223303388019261933988266126675740797091559541980722545880793708750882230374320698192373040882555154628949384420712168289605526223733016176898368282023301917856921049583659644200174763940543991507836551835324807116188739389620816364505209568211448815747330488813651206715564392791134964121857454359816296832013457790067067190116393364546525054134704119475840526673114964766611499226043189928040037210929720682839683846078550615582181112536768195193557758454282232948765374797970874053642822355832904812487562117265271449547063765654262549173209805579494164339236981348054782533307762260970390747872669357067489756517340817289701322583209366268084923373164395703994945233187987667632964509271169622904359262117908604555420100186491963838567445541249128944592555657626247", 10)
for range b.N {
if checkPrimeFactorsTooClose(n, rounds) != nil {
b.Fatal("factored the unfactorable!")
}
}
}
func BenchmarkFermat1(b *testing.B) { benchFermat(1, b) }
func BenchmarkFermat10(b *testing.B) { benchFermat(10, b) }
func BenchmarkFermat100(b *testing.B) { benchFermat(100, b) }
func BenchmarkFermat1000(b *testing.B) { benchFermat(1000, b) }
func BenchmarkFermat10000(b *testing.B) { benchFermat(10000, b) }