Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.

Commit 06b7804

Browse files
committed
Bug 1131767: Prune away paths using unacceptable algorithms earlier, r=keeler
--HG-- extra : rebase_source : 79efad2c5f60120ff1022547ce7efa628a7acd0f
1 parent 27cb600 commit 06b7804

8 files changed

Lines changed: 166 additions & 15 deletions

File tree

security/apps/AppTrustDomain.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,13 @@ AppTrustDomain::IsChainValid(const DERArray& certChain, Time time)
243243
return Success;
244244
}
245245

246+
Result
247+
AppTrustDomain::CheckSignatureDigestAlgorithm(DigestAlgorithm)
248+
{
249+
// TODO: We should restrict signatures to SHA-256 or better.
250+
return Success;
251+
}
252+
246253
Result
247254
AppTrustDomain::CheckRSAPublicKeyModulusSizeInBits(
248255
EndEntityOrCA /*endEntityOrCA*/, unsigned int modulusSizeInBits)
@@ -257,6 +264,7 @@ Result
257264
AppTrustDomain::VerifyRSAPKCS1SignedDigest(const SignedDigest& signedDigest,
258265
Input subjectPublicKeyInfo)
259266
{
267+
// TODO: We should restrict signatures to SHA-256 or better.
260268
return VerifyRSAPKCS1SignedDigestNSS(signedDigest, subjectPublicKeyInfo,
261269
mPinArg);
262270
}

security/apps/AppTrustDomain.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ class AppTrustDomain MOZ_FINAL : public mozilla::pkix::TrustDomain
3838
/*optional*/ const mozilla::pkix::Input* aiaExtension) MOZ_OVERRIDE;
3939
virtual Result IsChainValid(const mozilla::pkix::DERArray& certChain,
4040
mozilla::pkix::Time time) MOZ_OVERRIDE;
41+
virtual Result CheckSignatureDigestAlgorithm(
42+
mozilla::pkix::DigestAlgorithm digestAlg) MOZ_OVERRIDE;
4143
virtual Result CheckRSAPublicKeyModulusSizeInBits(
4244
mozilla::pkix::EndEntityOrCA endEntityOrCA,
4345
unsigned int modulusSizeInBits) MOZ_OVERRIDE;

security/certverifier/NSSCertDBTrustDomain.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,12 @@ NSSCertDBTrustDomain::IsChainValid(const DERArray& certArray, Time time)
712712
return Success;
713713
}
714714

715+
Result
716+
NSSCertDBTrustDomain::CheckSignatureDigestAlgorithm(DigestAlgorithm)
717+
{
718+
return Success;
719+
}
720+
715721
Result
716722
NSSCertDBTrustDomain::CheckRSAPublicKeyModulusSizeInBits(
717723
EndEntityOrCA /*endEntityOrCA*/, unsigned int modulusSizeInBits)

security/certverifier/NSSCertDBTrustDomain.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ class NSSCertDBTrustDomain : public mozilla::pkix::TrustDomain
7070
/*out*/ mozilla::pkix::TrustLevel& trustLevel)
7171
MOZ_OVERRIDE;
7272

73+
virtual Result CheckSignatureDigestAlgorithm(
74+
mozilla::pkix::DigestAlgorithm digestAlg) MOZ_OVERRIDE;
75+
7376
virtual Result CheckRSAPublicKeyModulusSizeInBits(
7477
mozilla::pkix::EndEntityOrCA endEntityOrCA,
7578
unsigned int modulusSizeInBits) MOZ_OVERRIDE;

security/pkix/include/pkix/pkixtypes.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,13 @@ class TrustDomain
271271
/*optional*/ const Input* stapledOCSPresponse,
272272
/*optional*/ const Input* aiaExtension) = 0;
273273

274+
// Check that the given digest algorithm is acceptable for use in signatures.
275+
//
276+
// Return Success if the algorithm is acceptable,
277+
// Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED if the algorithm is not
278+
// acceptable, or another error code if another error occurred.
279+
virtual Result CheckSignatureDigestAlgorithm(DigestAlgorithm digestAlg) = 0;
280+
274281
// Check that the RSA public key size is acceptable.
275282
//
276283
// Return Success if the key size is acceptable,

security/pkix/lib/pkixcheck.cpp

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,15 @@ namespace mozilla { namespace pkix {
3333
// 4.1.2.3 signature
3434

3535
Result
36-
CheckSignatureAlgorithm(Input signatureAlgorithmValue, Input signatureValue)
36+
CheckSignatureAlgorithm(TrustDomain& trustDomain,
37+
EndEntityOrCA endEntityOrCA,
38+
const der::SignedDataWithSignature& signedData,
39+
Input signatureValue)
3740
{
3841
// 4.1.1.2. signatureAlgorithm
3942
der::PublicKeyAlgorithm publicKeyAlg;
4043
DigestAlgorithm digestAlg;
41-
Reader signatureAlgorithmReader(signatureAlgorithmValue);
44+
Reader signatureAlgorithmReader(signedData.algorithm);
4245
Result rv = der::SignatureAlgorithmIdentifierValue(signatureAlgorithmReader,
4346
publicKeyAlg, digestAlg);
4447
if (rv != Success) {
@@ -78,6 +81,44 @@ CheckSignatureAlgorithm(Input signatureAlgorithmValue, Input signatureValue)
7881
return Result::ERROR_SIGNATURE_ALGORITHM_MISMATCH;
7982
}
8083

84+
// During the time of the deprecation of SHA-1 and the deprecation of RSA
85+
// keys of less than 2048 bits, we will encounter many certs signed using
86+
// SHA-1 and/or too-small RSA keys. With this in mind, we ask the trust
87+
// domain early on if it knows it will reject the signature purely based on
88+
// the digest algorithm and/or the RSA key size (if an RSA signature). This
89+
// is a good optimization because it completely avoids calling
90+
// trustDomain.FindIssuers (which may be slow) for such rejected certs, and
91+
// more generally it short-circuits any path building with them (which, of
92+
// course, is even slower).
93+
94+
rv = trustDomain.CheckSignatureDigestAlgorithm(digestAlg);
95+
if (rv != Success) {
96+
return rv;
97+
}
98+
99+
switch (publicKeyAlg) {
100+
case der::PublicKeyAlgorithm::RSA_PKCS1:
101+
{
102+
// The RSA computation may give a result that requires fewer bytes to
103+
// encode than the public key (since it is modular arithmetic). However,
104+
// the last step of generating a PKCS#1.5 signature is the I2OSP
105+
// procedure, which pads any such shorter result with zeros so that it
106+
// is exactly the same length as the public key.
107+
unsigned int signatureSizeInBits = signedData.signature.GetLength() * 8u;
108+
return trustDomain.CheckRSAPublicKeyModulusSizeInBits(
109+
endEntityOrCA, signatureSizeInBits);
110+
}
111+
112+
case der::PublicKeyAlgorithm::ECDSA:
113+
// In theory, we could implement a similar early-pruning optimization for
114+
// ECDSA curves. However, since there has been no similar deprecation for
115+
// for any curve that we support, the chances of us encountering a curve
116+
// during path building is too low to be worth bothering with.
117+
break;
118+
119+
MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
120+
}
121+
81122
return Success;
82123
}
83124

@@ -808,8 +849,8 @@ CheckIssuerIndependentProperties(TrustDomain& trustDomain,
808849

809850
switch (trustLevel) {
810851
case TrustLevel::InheritsTrust:
811-
rv = CheckSignatureAlgorithm(cert.GetSignedData().algorithm,
812-
cert.GetSignature());
852+
rv = CheckSignatureAlgorithm(trustDomain, endEntityOrCA,
853+
cert.GetSignedData(), cert.GetSignature());
813854
if (rv != Success) {
814855
return rv;
815856
}

security/pkix/test/gtest/pkixcheck_CheckSignatureAlgorithm_tests.cpp

Lines changed: 83 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,18 @@ using namespace mozilla::pkix::test;
3030

3131
namespace mozilla { namespace pkix {
3232

33-
extern Result CheckSignatureAlgorithm(Input signatureAlgorithmValue,
34-
Input signatureValue);
33+
extern Result CheckSignatureAlgorithm(
34+
TrustDomain& trustDomain, EndEntityOrCA endEntityOrCA,
35+
const der::SignedDataWithSignature& signedData,
36+
Input signatureValue);
3537

3638
} } // namespace mozilla::pkix
3739

3840
struct CheckSignatureAlgorithmTestParams
3941
{
4042
ByteString signatureAlgorithmValue;
4143
ByteString signatureValue;
44+
unsigned int signatureLengthInBytes;
4245
Result expectedResult;
4346
};
4447

@@ -76,86 +79,110 @@ static const CheckSignatureAlgorithmTestParams
7679
{ // Both algorithm IDs are empty
7780
ByteString(),
7881
ByteString(),
82+
2048 / 8,
7983
Result::ERROR_BAD_DER,
8084
},
8185
{ // signatureAlgorithm is empty, signature is supported.
8286
ByteString(),
8387
BS(tlv_sha256WithRSAEncryption),
88+
2048 / 8,
8489
Result::ERROR_BAD_DER,
8590
},
8691
{ // signatureAlgorithm is supported, signature is empty.
8792
BS(tlv_sha256WithRSAEncryption),
8893
ByteString(),
94+
2048 / 8,
8995
Result::ERROR_BAD_DER,
9096
},
9197
{ // Algorithms match, both are supported.
9298
BS(tlv_sha256WithRSAEncryption),
9399
BS(tlv_sha256WithRSAEncryption),
100+
2048 / 8,
94101
Success
95102
},
96103
{ // Algorithms do not match because signatureAlgorithm is truncated.
97104
BS(tlv_sha256WithRSAEncryption_truncated),
98105
BS(tlv_sha256WithRSAEncryption),
106+
2048 / 8,
99107
Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED
100108
},
101109
{ // Algorithms do not match because signature is truncated.
102110
BS(tlv_sha256WithRSAEncryption),
103111
BS(tlv_sha256WithRSAEncryption_truncated),
112+
2048 / 8,
104113
Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED
105114
},
106115
{ // Algorithms do not match, both are supported.
107116
BS(tlv_sha_1WithRSAEncryption),
108117
BS(tlv_sha256WithRSAEncryption),
118+
2048 / 8,
109119
Result::ERROR_SIGNATURE_ALGORITHM_MISMATCH,
110120
},
111121
{ // Algorithms do not match, both are supported.
112122
BS(tlv_sha256WithRSAEncryption),
113123
BS(tlv_sha_1WithRSAEncryption),
124+
2048 / 8,
114125
Result::ERROR_SIGNATURE_ALGORITHM_MISMATCH,
115126
},
116127
{ // Algorithms match, both are unsupported.
117128
BS(tlv_md5WithRSAEncryption),
118129
BS(tlv_md5WithRSAEncryption),
130+
2048 / 8,
119131
Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED
120132
},
121133
{ // signatureAlgorithm is unsupported, signature is supported.
122134
BS(tlv_md5WithRSAEncryption),
123135
BS(tlv_sha256WithRSAEncryption),
136+
2048 / 8,
124137
Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED
125138
},
126139
{ // signatureAlgorithm is supported, signature is unsupported.
127140
BS(tlv_sha256WithRSAEncryption),
128141
BS(tlv_md5WithRSAEncryption),
142+
2048 / 8,
129143
Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED
130144
},
131145
{ // Both have the optional NULL parameter.
132146
BS(tlv_sha256WithRSAEncryption) + TLV(der::NULLTag, ByteString()),
133147
BS(tlv_sha256WithRSAEncryption) + TLV(der::NULLTag, ByteString()),
148+
2048 / 8,
134149
Success
135150
},
136151
{ // signatureAlgorithm has the optional NULL parameter, signature doesn't.
137152
BS(tlv_sha256WithRSAEncryption) + TLV(der::NULLTag, ByteString()),
138153
BS(tlv_sha256WithRSAEncryption),
154+
2048 / 8,
139155
Success
140156
},
141157
{ // signatureAlgorithm does not have the optional NULL parameter, signature
142158
// does.
143159
BS(tlv_sha256WithRSAEncryption),
144160
BS(tlv_sha256WithRSAEncryption) + TLV(der::NULLTag, ByteString()),
161+
2048 / 8,
145162
Success
146163
},
147164
{ // The different OIDs for RSA-with-SHA1 we support are semantically
148165
// equivalent.
149166
BS(tlv_sha1WithRSASignature),
150167
BS(tlv_sha_1WithRSAEncryption),
168+
2048 / 8,
151169
Success,
152170
},
153171
{ // The different OIDs for RSA-with-SHA1 we support are semantically
154172
// equivalent (opposite order).
155173
BS(tlv_sha_1WithRSAEncryption),
156174
BS(tlv_sha1WithRSASignature),
175+
2048 / 8,
157176
Success,
158177
},
178+
{ // Algorithms match, both are supported, key size is not a multile of 128
179+
// bits. This test verifies that we're not wrongly rounding up the
180+
// signature size like we did in the original patch for bug 1131767.
181+
BS(tlv_sha256WithRSAEncryption),
182+
BS(tlv_sha256WithRSAEncryption),
183+
(2048 / 8) - 1,
184+
Success
185+
},
159186
};
160187

161188
class pkixcheck_CheckSignatureAlgorithm
@@ -164,6 +191,39 @@ class pkixcheck_CheckSignatureAlgorithm
164191
{
165192
};
166193

194+
class pkixcheck_CheckSignatureAlgorithm_TrustDomain final
195+
: public EverythingFailsByDefaultTrustDomain
196+
{
197+
public:
198+
explicit pkixcheck_CheckSignatureAlgorithm_TrustDomain(
199+
unsigned int publicKeySizeInBits)
200+
: publicKeySizeInBits(publicKeySizeInBits)
201+
, checkedDigestAlgorithm(false)
202+
, checkedModulusSizeInBits(false)
203+
{
204+
}
205+
206+
Result CheckSignatureDigestAlgorithm(DigestAlgorithm) override
207+
{
208+
checkedDigestAlgorithm = true;
209+
return Success;
210+
}
211+
212+
Result CheckRSAPublicKeyModulusSizeInBits(EndEntityOrCA endEntityOrCA,
213+
unsigned int modulusSizeInBits)
214+
override
215+
{
216+
EXPECT_EQ(EndEntityOrCA::MustBeEndEntity, endEntityOrCA);
217+
EXPECT_EQ(publicKeySizeInBits, modulusSizeInBits);
218+
checkedModulusSizeInBits = true;
219+
return Success;
220+
}
221+
222+
const unsigned int publicKeySizeInBits;
223+
bool checkedDigestAlgorithm;
224+
bool checkedModulusSizeInBits;
225+
};
226+
167227
TEST_P(pkixcheck_CheckSignatureAlgorithm, CheckSignatureAlgorithm)
168228
{
169229
const CheckSignatureAlgorithmTestParams& params(GetParam());
@@ -173,26 +233,37 @@ TEST_P(pkixcheck_CheckSignatureAlgorithm, CheckSignatureAlgorithm)
173233
signatureValueInput.Init(params.signatureValue.data(),
174234
params.signatureValue.length()));
175235

176-
Input signatureAlgorithmValueInput;
236+
pkixcheck_CheckSignatureAlgorithm_TrustDomain
237+
trustDomain(params.signatureLengthInBytes * 8);
238+
239+
der::SignedDataWithSignature signedData;
240+
ASSERT_EQ(Success,
241+
signedData.algorithm.Init(params.signatureAlgorithmValue.data(),
242+
params.signatureAlgorithmValue.length()));
243+
244+
ByteString dummySignature(params.signatureLengthInBytes, 0xDE);
177245
ASSERT_EQ(Success,
178-
signatureAlgorithmValueInput.Init(
179-
params.signatureAlgorithmValue.data(),
180-
params.signatureAlgorithmValue.length()));
246+
signedData.signature.Init(dummySignature.data(),
247+
dummySignature.length()));
181248

182249
ASSERT_EQ(params.expectedResult,
183-
CheckSignatureAlgorithm(signatureAlgorithmValueInput,
184-
signatureValueInput));
250+
CheckSignatureAlgorithm(trustDomain, EndEntityOrCA::MustBeEndEntity,
251+
signedData, signatureValueInput));
252+
ASSERT_EQ(params.expectedResult == Success,
253+
trustDomain.checkedDigestAlgorithm);
254+
ASSERT_EQ(params.expectedResult == Success,
255+
trustDomain.checkedModulusSizeInBits);
185256
}
186257

187258
INSTANTIATE_TEST_CASE_P(
188259
pkixcheck_CheckSignatureAlgorithm, pkixcheck_CheckSignatureAlgorithm,
189260
testing::ValuesIn(CHECKSIGNATUREALGORITHM_TEST_PARAMS));
190261

191-
class pkixcheck_CheckSignatureAlgorithmTrustDomain
262+
class pkixcheck_CheckSignatureAlgorithm_BuildCertChain_TrustDomain
192263
: public DefaultCryptoTrustDomain
193264
{
194265
public:
195-
explicit pkixcheck_CheckSignatureAlgorithmTrustDomain(
266+
explicit pkixcheck_CheckSignatureAlgorithm_BuildCertChain_TrustDomain(
196267
const ByteString& issuer)
197268
: issuer(issuer)
198269
{
@@ -275,7 +346,8 @@ TEST_F(pkixcheck_CheckSignatureAlgorithm, BuildCertChain)
275346

276347
Input subjectInput;
277348
ASSERT_EQ(Success, subjectInput.Init(subject.data(), subject.length()));
278-
pkixcheck_CheckSignatureAlgorithmTrustDomain trustDomain(issuer);
349+
pkixcheck_CheckSignatureAlgorithm_BuildCertChain_TrustDomain
350+
trustDomain(issuer);
279351
Result rv = BuildCertChain(trustDomain, subjectInput, Now(),
280352
EndEntityOrCA::MustBeEndEntity,
281353
KeyUsage::noParticularKeyUsageRequired,

security/pkix/test/gtest/pkixgtest.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,13 @@ class EverythingFailsByDefaultTrustDomain : public TrustDomain
125125
Result::FATAL_ERROR_LIBRARY_FAILURE);
126126
}
127127

128+
Result CheckSignatureDigestAlgorithm(DigestAlgorithm) override
129+
{
130+
ADD_FAILURE();
131+
return NotReached("CheckSignatureDigestAlgorithm should not be called",
132+
Result::FATAL_ERROR_LIBRARY_FAILURE);
133+
}
134+
128135
Result CheckECDSACurveIsAcceptable(EndEntityOrCA, NamedCurve) override
129136
{
130137
ADD_FAILURE();
@@ -163,6 +170,11 @@ class DefaultCryptoTrustDomain : public EverythingFailsByDefaultTrustDomain
163170
return TestDigestBuf(item, digestAlg, digestBuf, digestBufLen);
164171
}
165172

173+
Result CheckSignatureDigestAlgorithm(DigestAlgorithm) override
174+
{
175+
return Success;
176+
}
177+
166178
Result CheckECDSACurveIsAcceptable(EndEntityOrCA, NamedCurve) override
167179
{
168180
return Success;

0 commit comments

Comments
 (0)