From 5df083a57ef41c4cc877e534f0c47a17aed4d487 Mon Sep 17 00:00:00 2001 From: Jacob Hoffman-Andrews Date: Thu, 2 Nov 2017 05:42:59 -0700 Subject: [PATCH] Add ROCA weak key checking (#3189) Thanks to @titanous for the library! --- Godeps/Godeps.json | 4 ++ features/featureflag_string.go | 4 +- features/features.go | 2 + goodkey/good_key.go | 7 +++ goodkey/good_key_test.go | 18 +++++++ test/config-next/ra.json | 1 + test/config-next/wfe.json | 1 + vendor/github.com/titanous/rocacheck/LICENSE | 22 ++++++++ .../github.com/titanous/rocacheck/README.md | 7 +++ .../titanous/rocacheck/rocacheck.go | 52 +++++++++++++++++++ 10 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 vendor/github.com/titanous/rocacheck/LICENSE create mode 100644 vendor/github.com/titanous/rocacheck/README.md create mode 100644 vendor/github.com/titanous/rocacheck/rocacheck.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 4fa14f7fa..3308b8e84 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -232,6 +232,10 @@ "Comment": "v1.1.3-8-g0744955", "Rev": "0744955171b0b6e1871ff9d7366abc2b7a7fcec0" }, + { + "ImportPath": "github.com/titanous/rocacheck", + "Rev": "afe73141d399b0c79c3d6412d4bdcf4c672c496a" + }, { "ImportPath": "github.com/weppos/publicsuffix-go/publicsuffix", "Comment": "v0.3.2-24-g02da67f", diff --git a/features/featureflag_string.go b/features/featureflag_string.go index 4d68016a4..d0f4cdb0e 100644 --- a/features/featureflag_string.go +++ b/features/featureflag_string.go @@ -4,9 +4,9 @@ package features import "fmt" -const _FeatureFlag_name = "unusedAllowAccountDeactivationAllowKeyRolloverResubmitMissingSCTsOnlyUseAIAIssuerURLAllowTLS02ChallengesGenerateOCSPEarlyReusePendingAuthzCountCertificatesExactRandomDirectoryEntryIPv6FirstDirectoryMetaAllowRenewalFirstRLRecheckCAALegacyCAAUDPDNS" +const _FeatureFlag_name = "unusedAllowAccountDeactivationAllowKeyRolloverResubmitMissingSCTsOnlyUseAIAIssuerURLAllowTLS02ChallengesGenerateOCSPEarlyReusePendingAuthzCountCertificatesExactRandomDirectoryEntryIPv6FirstDirectoryMetaAllowRenewalFirstRLRecheckCAALegacyCAAUDPDNSROCACheck" -var _FeatureFlag_index = [...]uint8{0, 6, 30, 46, 69, 84, 104, 121, 138, 160, 180, 189, 202, 221, 231, 240, 246} +var _FeatureFlag_index = [...]uint8{0, 6, 30, 46, 69, 84, 104, 121, 138, 160, 180, 189, 202, 221, 231, 240, 246, 255} func (i FeatureFlag) String() string { if i < 0 || i >= FeatureFlag(len(_FeatureFlag_index)-1) { diff --git a/features/features.go b/features/features.go index 12f71c011..c9a6deeef 100644 --- a/features/features.go +++ b/features/features.go @@ -29,6 +29,7 @@ const ( RecheckCAA LegacyCAA UDPDNS + ROCACheck ) // List of features and their default value, protected by fMu @@ -49,6 +50,7 @@ var features = map[FeatureFlag]bool{ RecheckCAA: false, LegacyCAA: false, UDPDNS: false, + ROCACheck: false, } var fMu = new(sync.RWMutex) diff --git a/goodkey/good_key.go b/goodkey/good_key.go index d38253235..a7dfee386 100644 --- a/goodkey/good_key.go +++ b/goodkey/good_key.go @@ -10,6 +10,8 @@ import ( "sync" berrors "github.com/letsencrypt/boulder/errors" + "github.com/letsencrypt/boulder/features" + "github.com/titanous/rocacheck" ) // To generate, run: primes 2 752 | tr '\n' , @@ -233,6 +235,11 @@ func (policy *KeyPolicy) goodKeyRSA(key rsa.PublicKey) (err error) { if checkSmallPrimes(modulus) { return berrors.MalformedError("key divisible by small prime") } + // Check for weak keys generated by Infineon hardware + // (see https://crocs.fi.muni.cz/public/papers/rsa_ccs17) + if features.Enabled(features.ROCACheck) && rocacheck.IsWeak(&key) { + return berrors.MalformedError("key generated by vulnerable Infineon-based hardware") + } return nil } diff --git a/goodkey/good_key_test.go b/goodkey/good_key_test.go index c42cfca05..f5ab4f8b4 100644 --- a/goodkey/good_key_test.go +++ b/goodkey/good_key_test.go @@ -5,9 +5,11 @@ import ( "crypto/elliptic" "crypto/rand" "crypto/rsa" + "log" "math/big" "testing" + "github.com/letsencrypt/boulder/features" "github.com/letsencrypt/boulder/test" ) @@ -84,6 +86,22 @@ func TestModulusDivisibleBy752(t *testing.T) { test.AssertError(t, testingPolicy.GoodKey(&key), "Should have rejected modulus divisible by 751.") } +func TestROCA(t *testing.T) { + err := features.Set(map[string]bool{"ROCACheck": true}) + if err != nil { + log.Fatal(err) + } + n, ok := big.NewInt(1).SetString("19089470491547632015867380494603366846979936677899040455785311493700173635637619562546319438505971838982429681121352968394792665704951454132311441831732124044135181992768774222852895664400681270897445415599851900461316070972022018317962889565731866601557238345786316235456299813772607869009873279585912430769332375239444892105064608255089298943707214066350230292124208314161171265468111771687514518823144499250339825049199688099820304852696380797616737008621384107235756455735861506433065173933123259184114000282435500939123478591192413006994709825840573671701120771013072419520134975733578923370992644987545261926257", 10) + if !ok { + t.Fatal("failed to parse") + } + key := rsa.PublicKey{ + N: n, + E: 65537, + } + test.AssertError(t, testingPolicy.GoodKey(&key), "Should have rejected ROCA-weak key.") +} + func TestGoodKey(t *testing.T) { private, err := rsa.GenerateKey(rand.Reader, 2048) test.AssertNotError(t, err, "Error generating key") diff --git a/test/config-next/ra.json b/test/config-next/ra.json index 052599313..d0b34baf1 100644 --- a/test/config-next/ra.json +++ b/test/config-next/ra.json @@ -42,6 +42,7 @@ ] }, "features": { + "ROCACheck": true, "UDPDNS": true, "AllowKeyRollover": true, "AllowTLS02Challenges": true, diff --git a/test/config-next/wfe.json b/test/config-next/wfe.json index 07794e1f7..efd59d946 100644 --- a/test/config-next/wfe.json +++ b/test/config-next/wfe.json @@ -29,6 +29,7 @@ "timeout": "15s" }, "features": { + "ROCACheck": true, "AllowAccountDeactivation": true, "AllowKeyRollover": true, "UseAIAIssuerURL": true, diff --git a/vendor/github.com/titanous/rocacheck/LICENSE b/vendor/github.com/titanous/rocacheck/LICENSE new file mode 100644 index 000000000..7bdce481f --- /dev/null +++ b/vendor/github.com/titanous/rocacheck/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2017, Jonathan Rudenberg +Copyright (c) 2017, CRoCS, EnigmaBridge Ltd. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/titanous/rocacheck/README.md b/vendor/github.com/titanous/rocacheck/README.md new file mode 100644 index 000000000..b8e765ea9 --- /dev/null +++ b/vendor/github.com/titanous/rocacheck/README.md @@ -0,0 +1,7 @@ +# rocacheck [![GoDoc](https://godoc.org/github.com/titanous/rocacheck?status.svg)](https://godoc.org/github.com/titanous/rocacheck) + +Package rocacheck is a Go implementation of the [key fingerprint +algorithm](https://github.com/crocs-muni/roca) that checks if an RSA key was +generated by broken Infineon code and is vulnerable to factorization via the +[Return of Coppersmith's Attack +(ROCA)](https://crocs.fi.muni.cz/public/papers/rsa_ccs17) / CVE-2017-15361. diff --git a/vendor/github.com/titanous/rocacheck/rocacheck.go b/vendor/github.com/titanous/rocacheck/rocacheck.go new file mode 100644 index 000000000..e813579bb --- /dev/null +++ b/vendor/github.com/titanous/rocacheck/rocacheck.go @@ -0,0 +1,52 @@ +// Package rocacheck checks if a key was generated by broken Infineon code and +// is vulnerable to factorization via the Return of Coppersmith's Attack (ROCA) +// / CVE-2017-15361. +package rocacheck + +import ( + "crypto/rsa" + "math/big" +) + +type test struct { + Prime *big.Int + Fingerprints map[int64]struct{} +} + +var tests = make([]test, 17) + +func init() { + bigOne := big.NewInt(1) + n := &big.Int{} + // relations table from https://github.com/crocs-muni/roca/pull/40 + for i, r := range [][2]int64{ + {2, 11}, {6, 13}, {8, 17}, {9, 19}, {3, 37}, {26, 53}, {20, 61}, + {35, 71}, {24, 73}, {13, 79}, {6, 97}, {51, 103}, {53, 107}, + {54, 109}, {42, 127}, {50, 151}, {78, 157}, + } { + fps := make(map[int64]struct{}) + bp := big.NewInt(r[1]) + br := big.NewInt(r[0]) + for j := int64(0); j < r[1]; j++ { + if n.Exp(big.NewInt(j), br, bp).Cmp(bigOne) == 0 { + fps[j] = struct{}{} + } + } + tests[i] = test{ + Prime: big.NewInt(r[1]), + Fingerprints: fps, + } + } +} + +// IsWeak returns true if a RSA public key is vulnerable to Return of +// Coppersmith's Attack (ROCA). +func IsWeak(k *rsa.PublicKey) bool { + tmp := &big.Int{} + for _, t := range tests { + if _, ok := t.Fingerprints[tmp.Mod(k.N, t.Prime).Int64()]; !ok { + return false + } + } + return true +}