pkcs12: limit PBKDF iteration count to prevent CPU exhaustion

The PKCS#12 PBKDF iteration count is read directly from
the input file with no upper bound. A crafted 83-byte .p12
file can set iterations to 2^31-1 (2147483647), causing
Decode() to block a CPU core permanently.

This change adds a maximum iteration limit of 1000000 in
both verifyMac and pbDecrypterFor. Any file that specifies
more iterations than this cap is rejected with an error.

For reference, OpenSSL caps PBKDF2 at 10000000 iterations,
and scrypt is bounded by its memory-hardness parameters.
The 1000000 limit is generous for legitimate PKCS#12 files
while still preventing denial of service.

Fixes golang/go#78524

Change-Id: I1d12d6f5d90e347ca0dc113678abc51b0cff8d85
GitHub-Last-Rev: a0370d492dd4735cc7c1bb082b359ad79863fdae
GitHub-Pull-Request: golang/crypto#343
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/759900
Reviewed-by: Daniel McCarney <daniel@binaryparadox.net>
Reviewed-by: David Chase <drchase@google.com>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
mohammadmseet-hue
2026-06-04 06:58:05 -07:00
committed by Daniel McCarney
parent e2ffffe738
commit d37c95e27d
4 changed files with 94 additions and 0 deletions
+4
View File
@@ -80,6 +80,10 @@ func pbDecrypterFor(algorithm pkix.AlgorithmIdentifier, password []byte) (cipher
return nil, 0, err
}
if params.Iterations < 0 || params.Iterations > maxIterations {
return nil, 0, NotImplementedError("iteration count is invalid or too high")
}
key := cipherType.deriveKey(params.Salt, password, params.Iterations)
iv := cipherType.deriveIV(params.Salt, password, params.Iterations)
+40
View File
@@ -13,6 +13,46 @@ import (
var sha1WithTripleDES = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 12, 1, 3})
func TestPbDecrypterForIterationLimit(t *testing.T) {
pass, _ := bmpString("Sesame open")
tests := []struct {
name string
iterations int
wantErr bool
}{
{"at limit", maxIterations, false},
{"over limit", maxIterations + 1, true},
{"negative", -1, true},
{"max int", 1<<31 - 1, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
params, _ := asn1.Marshal(pbeParams{
Salt: []byte{1, 2, 3, 4, 5, 6, 7, 8},
Iterations: tt.iterations,
})
alg := pkix.AlgorithmIdentifier{
Algorithm: sha1WithTripleDES,
Parameters: asn1.RawValue{
FullBytes: params,
},
}
_, _, err := pbDecrypterFor(alg, pass)
if tt.wantErr {
if _, ok := err.(NotImplementedError); !ok {
t.Errorf("iterations=%d: got %v, want NotImplementedError", tt.iterations, err)
}
} else {
if err != nil {
t.Errorf("iterations=%d: unexpected error: %v", tt.iterations, err)
}
}
})
}
}
func TestPbDecrypterFor(t *testing.T) {
params, _ := asn1.Marshal(pbeParams{
Salt: []byte{1, 2, 3, 4, 5, 6, 7, 8},
+8
View File
@@ -27,11 +27,19 @@ var (
oidSHA1 = asn1.ObjectIdentifier([]int{1, 3, 14, 3, 2, 26})
)
// maxIterations is a safety limit to prevent CPU exhaustion from
// crafted PKCS#12 files with unreasonable iteration counts.
const maxIterations = 1 << 20 // ~1 million
func verifyMac(macData *macData, message, password []byte) error {
if !macData.Mac.Algorithm.Algorithm.Equal(oidSHA1) {
return NotImplementedError("unknown digest algorithm: " + macData.Mac.Algorithm.Algorithm.String())
}
if macData.Iterations < 0 || macData.Iterations > maxIterations {
return NotImplementedError("iteration count is invalid or too high")
}
key := pbkdf(sha1Sum, 20, 64, macData.MacSalt, password, macData.Iterations, 3, 20)
mac := hmac.New(sha1.New, key)
+42
View File
@@ -5,10 +5,52 @@
package pkcs12
import (
"crypto/x509/pkix"
"encoding/asn1"
"testing"
)
func TestVerifyMacIterationLimit(t *testing.T) {
password, _ := bmpString("Sesame open")
message := []byte{11, 12, 13, 14, 15}
tests := []struct {
name string
iterations int
wantErr bool
}{
{"at limit", maxIterations, false},
{"over limit", maxIterations + 1, true},
{"negative", -1, true},
{"max int", 1<<31 - 1, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
td := macData{
Mac: digestInfo{
Algorithm: pkix.AlgorithmIdentifier{
Algorithm: oidSHA1,
},
Digest: nil, // will fail MAC check, but iteration check comes first
},
MacSalt: []byte{1, 2, 3, 4, 5, 6, 7, 8},
Iterations: tt.iterations,
}
err := verifyMac(&td, message, password)
if tt.wantErr {
if _, ok := err.(NotImplementedError); !ok {
t.Errorf("iterations=%d: got %v, want NotImplementedError", tt.iterations, err)
}
} else {
if _, ok := err.(NotImplementedError); ok {
t.Errorf("iterations=%d: got unexpected NotImplementedError", tt.iterations)
}
}
})
}
}
func TestVerifyMac(t *testing.T) {
td := macData{
Mac: digestInfo{