@@ -6,25 +6,34 @@ package pkcs12
66
77import (
88 "bytes"
9+ "crypto/aes"
910 "crypto/cipher"
1011 "crypto/des"
1112 "crypto/rand"
13+ "crypto/sha256"
1214 "crypto/x509/pkix"
1315 "encoding/asn1"
1416 "errors"
1517 "io"
1618
1719 "github.com/hashicorp/packer-plugin-azure/builder/azure/pkcs12/rc2"
20+ "golang.org/x/crypto/pbkdf2"
1821)
1922
2023const (
21- pbeIterationCount = 2048
22- pbeSaltSizeBytes = 8
24+ pbeIterationCount = 2048
25+ pbeIterationCountModern = 100000 // OWASP 2021 baseline for PBKDF2, balances security and performance
26+ pbeSaltSizeBytes = 8
2327)
2428
2529var (
2630 oidPBEWithSHAAnd3KeyTripleDESCBC = asn1 .ObjectIdentifier ([]int {1 , 2 , 840 , 113549 , 1 , 12 , 1 , 3 })
2731 oidPBEWithSHAAnd40BitRC2CBC = asn1 .ObjectIdentifier ([]int {1 , 2 , 840 , 113549 , 1 , 12 , 1 , 6 })
32+ // PBES2 (Password-Based Encryption Scheme 2) from PKCS#5 v2.0
33+ oidPBES2 = asn1 .ObjectIdentifier ([]int {1 , 2 , 840 , 113549 , 1 , 5 , 13 })
34+ oidPBKDF2 = asn1 .ObjectIdentifier ([]int {1 , 2 , 840 , 113549 , 1 , 5 , 12 })
35+ oidHMACSHA256 = asn1 .ObjectIdentifier ([]int {1 , 2 , 840 , 113549 , 2 , 9 })
36+ oidAES256CBC = asn1 .ObjectIdentifier ([]int {2 , 16 , 840 , 1 , 101 , 3 , 4 , 1 , 42 })
2837)
2938
3039// pbeCipher is an abstraction of a PKCS#12 cipher.
@@ -70,9 +79,37 @@ type pbeParams struct {
7079 Iterations int
7180}
7281
82+ // PBES2 parameters as per PKCS#5 v2.0
83+ type pbes2Params struct {
84+ KeyDerivationFunc pkix.AlgorithmIdentifier
85+ EncryptionScheme pkix.AlgorithmIdentifier
86+ }
87+
88+ type pbkdf2Params struct {
89+ Salt []byte
90+ IterationCount int
91+ KeyLength int `asn1:"optional"`
92+ PRF pkix.AlgorithmIdentifier `asn1:"optional"`
93+ }
94+
95+ type aes256CBCParams struct {
96+ IV []byte
97+ }
98+
7399func pbDecrypterFor (algorithm pkix.AlgorithmIdentifier , password []byte ) (cipher.BlockMode , int , error ) {
100+ // Check if this is PBES2 (modern AES-256)
101+ if algorithm .Algorithm .Equal (oidPBES2 ) {
102+ return pbes2Decrypter (algorithm , password )
103+ }
104+
105+ // Legacy PBES1 algorithms
74106 var cipherType pbeCipher
75107
108+ var params pbeParams
109+ if err := unmarshal (algorithm .Parameters .FullBytes , & params ); err != nil {
110+ return nil , 0 , err
111+ }
112+
76113 switch {
77114 case algorithm .Algorithm .Equal (oidPBEWithSHAAnd3KeyTripleDESCBC ):
78115 cipherType = shaWithTripleDESCBC {}
@@ -82,11 +119,6 @@ func pbDecrypterFor(algorithm pkix.AlgorithmIdentifier, password []byte) (cipher
82119 return nil , 0 , NotImplementedError ("algorithm " + algorithm .Algorithm .String () + " is not supported" )
83120 }
84121
85- var params pbeParams
86- if err := unmarshal (algorithm .Parameters .FullBytes , & params ); err != nil {
87- return nil , 0 , err
88- }
89-
90122 key := cipherType .deriveKey (params .Salt , password , params .Iterations )
91123 iv := cipherType .deriveIV (params .Salt , password , params .Iterations )
92124
@@ -98,6 +130,46 @@ func pbDecrypterFor(algorithm pkix.AlgorithmIdentifier, password []byte) (cipher
98130 return cipher .NewCBCDecrypter (block , iv ), block .BlockSize (), nil
99131}
100132
133+ func pbes2Decrypter (algorithm pkix.AlgorithmIdentifier , password []byte ) (cipher.BlockMode , int , error ) {
134+ var params pbes2Params
135+ if err := unmarshal (algorithm .Parameters .FullBytes , & params ); err != nil {
136+ return nil , 0 , errors .New ("pkcs12: invalid PBES2 parameters: " + err .Error ())
137+ }
138+
139+ // Verify we're using PBKDF2
140+ if ! params .KeyDerivationFunc .Algorithm .Equal (oidPBKDF2 ) {
141+ return nil , 0 , NotImplementedError ("only PBKDF2 is supported for PBES2" )
142+ }
143+
144+ // Parse PBKDF2 parameters
145+ var kdfParams pbkdf2Params
146+ if err := unmarshal (params .KeyDerivationFunc .Parameters .FullBytes , & kdfParams ); err != nil {
147+ return nil , 0 , errors .New ("pkcs12: invalid PBKDF2 parameters: " + err .Error ())
148+ }
149+
150+ // Verify we're using AES-256-CBC
151+ if ! params .EncryptionScheme .Algorithm .Equal (oidAES256CBC ) {
152+ return nil , 0 , NotImplementedError ("only AES-256-CBC is supported for PBES2" )
153+ }
154+
155+ // Parse AES parameters (IV)
156+ var aesParams aes256CBCParams
157+ if err := unmarshal (params .EncryptionScheme .Parameters .FullBytes , & aesParams ); err != nil {
158+ return nil , 0 , errors .New ("pkcs12: invalid AES parameters: " + err .Error ())
159+ }
160+
161+ // Derive key using PBKDF2
162+ key := pbkdf2 .Key (password , kdfParams .Salt , kdfParams .IterationCount , 32 , sha256 .New )
163+
164+ // Create AES cipher
165+ block , err := aes .NewCipher (key )
166+ if err != nil {
167+ return nil , 0 , err
168+ }
169+
170+ return cipher .NewCBCDecrypter (block , aesParams .IV ), block .BlockSize (), nil
171+ }
172+
101173func pbDecrypt (info decryptable , password []byte ) (decrypted []byte , err error ) {
102174 cbc , blockSize , err := pbDecrypterFor (info .Algorithm (), password )
103175 if err != nil {
@@ -137,6 +209,8 @@ func pad(src []byte, blockSize int) []byte {
137209 return append (src , paddingText ... )
138210}
139211
212+ // pbEncrypt encrypts plainText using legacy Triple DES algorithm with low iteration count.
213+ // Deprecated: Use pbEncryptModern for stronger security with high iteration count instead.
140214func pbEncrypt (plainText , salt , password []byte , iterations int ) (cipherText []byte , err error ) {
141215 if _ , err := io .ReadFull (rand .Reader , salt ); err != nil {
142216 return nil , errors .New ("pkcs12: failed to create a random salt value: " + err .Error ())
@@ -160,6 +234,93 @@ func pbEncrypt(plainText, salt, password []byte, iterations int) (cipherText []b
160234 return cipherText , nil
161235}
162236
237+ // pbEncryptModern encrypts plainText using AES-256-CBC with PBES2 (PKCS#5 v2.0).
238+ // This provides modern encryption that is compatible with Windows and Azure while
239+ // offering much stronger security than legacy Triple DES.
240+ // Uses 100,000 iterations (OWASP 2021 baseline), providing strong security while
241+ // maintaining reasonable performance for ephemeral certificates.
242+ // Returns the ciphertext and the IV used (needed for PBES2 parameters).
243+ func pbEncryptModern (plainText , salt , password []byte , iterations int ) (cipherText , iv []byte , err error ) {
244+ if _ , err := io .ReadFull (rand .Reader , salt ); err != nil {
245+ return nil , nil , errors .New ("pkcs12: failed to create a random salt value: " + err .Error ())
246+ }
247+
248+ // Generate IV for AES-256-CBC
249+ iv = make ([]byte , aes .BlockSize )
250+ if _ , err := io .ReadFull (rand .Reader , iv ); err != nil {
251+ return nil , nil , errors .New ("pkcs12: failed to create IV: " + err .Error ())
252+ }
253+
254+ // Derive key using PBKDF2 with SHA-256
255+ key := pbkdf2 .Key (password , salt , iterations , 32 , sha256 .New )
256+
257+ // Create AES cipher
258+ block , err := aes .NewCipher (key )
259+ if err != nil {
260+ return nil , nil , errors .New ("pkcs12: failed to create AES cipher: " + err .Error ())
261+ }
262+
263+ paddedPlainText := pad (plainText , block .BlockSize ())
264+
265+ encrypter := cipher .NewCBCEncrypter (block , iv )
266+ cipherText = make ([]byte , len (paddedPlainText ))
267+ encrypter .CryptBlocks (cipherText , paddedPlainText )
268+
269+ return cipherText , iv , nil
270+ }
271+
272+ // pbEncryptModernParams returns PBES2 algorithm parameters for AES-256-CBC encryption
273+ func pbEncryptModernParams (salt , iv []byte , iterations int ) (asn1.RawValue , error ) {
274+ // PBKDF2 parameters
275+ pbkdf2Params := pbkdf2Params {
276+ Salt : salt ,
277+ IterationCount : iterations ,
278+ KeyLength : 32 , // AES-256 requires 32 bytes
279+ PRF : pkix.AlgorithmIdentifier {
280+ Algorithm : oidHMACSHA256 ,
281+ },
282+ }
283+
284+ pbkdf2ParamsBytes , err := asn1 .Marshal (pbkdf2Params )
285+ if err != nil {
286+ return asn1.RawValue {}, err
287+ }
288+
289+ var pbkdf2ParamsRaw asn1.RawValue
290+ if _ , err := asn1 .Unmarshal (pbkdf2ParamsBytes , & pbkdf2ParamsRaw ); err != nil {
291+ return asn1.RawValue {}, err
292+ }
293+
294+ // AES-256-CBC parameters (just the IV)
295+ aesParams := aes256CBCParams {
296+ IV : iv ,
297+ }
298+
299+ aesParamsBytes , err := asn1 .Marshal (aesParams )
300+ if err != nil {
301+ return asn1.RawValue {}, err
302+ }
303+
304+ var aesParamsRaw asn1.RawValue
305+ if _ , err := asn1 .Unmarshal (aesParamsBytes , & aesParamsRaw ); err != nil {
306+ return asn1.RawValue {}, err
307+ }
308+
309+ // PBES2 parameters
310+ pbes2Params := pbes2Params {
311+ KeyDerivationFunc : pkix.AlgorithmIdentifier {
312+ Algorithm : oidPBKDF2 ,
313+ Parameters : pbkdf2ParamsRaw ,
314+ },
315+ EncryptionScheme : pkix.AlgorithmIdentifier {
316+ Algorithm : oidAES256CBC ,
317+ Parameters : aesParamsRaw ,
318+ },
319+ }
320+
321+ return convertToRawVal (pbes2Params )
322+ }
323+
163324// decryptable abstracts a object that contains ciphertext.
164325type decryptable interface {
165326 Algorithm () pkix.AlgorithmIdentifier
0 commit comments