From d37c95e27de65576f42440cdfbc261d810506841 Mon Sep 17 00:00:00 2001 From: mohammadmseet-hue Date: Mon, 6 Apr 2026 20:10:40 +0000 Subject: [PATCH] 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 Reviewed-by: David Chase Reviewed-by: Junyang Shao LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com --- pkcs12/crypto.go | 4 ++++ pkcs12/crypto_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ pkcs12/mac.go | 8 ++++++++ pkcs12/mac_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 94 insertions(+) diff --git a/pkcs12/crypto.go b/pkcs12/crypto.go index 212538c..3f30732 100644 --- a/pkcs12/crypto.go +++ b/pkcs12/crypto.go @@ -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) diff --git a/pkcs12/crypto_test.go b/pkcs12/crypto_test.go index eb4dae8..29962ec 100644 --- a/pkcs12/crypto_test.go +++ b/pkcs12/crypto_test.go @@ -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}, diff --git a/pkcs12/mac.go b/pkcs12/mac.go index 5f38aa7..84039ce 100644 --- a/pkcs12/mac.go +++ b/pkcs12/mac.go @@ -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) diff --git a/pkcs12/mac_test.go b/pkcs12/mac_test.go index 1ed4ff2..5d4a7ec 100644 --- a/pkcs12/mac_test.go +++ b/pkcs12/mac_test.go @@ -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{