Skip to content

Commit a82c6a9

Browse files
committed
crypto
1 parent 864bcd1 commit a82c6a9

6 files changed

Lines changed: 356 additions & 9 deletions

File tree

builder/azure/arm/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -817,7 +817,7 @@ func (c *Config) formatCertificateForKeyVault(privateKey *rsa.PrivateKey) (strin
817817
return "", err
818818
}
819819

820-
pfxBytes, err := pkcs12.Encode(derBytes, privateKey, c.tmpCertificatePassword)
820+
pfxBytes, err := pkcs12.EncodeModern(derBytes, privateKey, c.tmpCertificatePassword)
821821
if err != nil {
822822
err = fmt.Errorf("Failed to encode certificate as PFX: %s", err)
823823
return "", err

builder/azure/dtl/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ func (c *Config) createCertificate() (string, string, error) {
377377
return "", "", err
378378
}
379379

380-
pfxBytes, err := pkcs12.Encode(derBytes, privateKey, c.tmpCertificatePassword)
380+
pfxBytes, err := pkcs12.EncodeModern(derBytes, privateKey, c.tmpCertificatePassword)
381381
if err != nil {
382382
err = fmt.Errorf("Failed to encode certificate as PFX: %s", err)
383383
return "", "", err

builder/azure/pkcs12/crypto.go

Lines changed: 168 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,34 @@ package pkcs12
66

77
import (
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

2023
const (
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

2529
var (
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+
7399
func 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+
101173
func 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.
140214
func 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.
164325
type decryptable interface {
165326
Algorithm() pkix.AlgorithmIdentifier

builder/azure/pkcs12/pkcs12.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,40 @@ func makeContentInfos(derBytes []byte, privateKey interface{}, password []byte)
381381
return contentInfos, nil
382382
}
383383

384+
// makeShroudedKeyBagContentInfoModern creates content info using AES-256-CBC with PBES2.
385+
func makeShroudedKeyBagContentInfoModern(privateKey interface{}, password []byte) (*contentInfo, error) {
386+
shroudedKeyBagBytes, err := encodePkcs8ShroudedKeyBagModern(privateKey, password)
387+
if err != nil {
388+
return nil, EncodeError("encode PKCS#8 shrouded key bag: " + err.Error())
389+
}
390+
391+
safeBags, err := makeSafeBags(oidPKCS8ShroudedKeyBag, shroudedKeyBagBytes)
392+
if err != nil {
393+
return nil, EncodeError("safe bags: " + err.Error())
394+
}
395+
396+
return makeContentInfo(safeBags)
397+
}
398+
399+
// makeContentInfosModern creates content infos using AES-256-CBC with PBES2.
400+
func makeContentInfosModern(derBytes []byte, privateKey interface{}, password []byte) ([]contentInfo, error) {
401+
shroudedKeyContentInfo, err := makeShroudedKeyBagContentInfoModern(privateKey, password)
402+
if err != nil {
403+
return nil, EncodeError("shrouded key content info: " + err.Error())
404+
}
405+
406+
certBagContentInfo, err := makeCertBagContentInfo(derBytes)
407+
if err != nil {
408+
return nil, EncodeError("cert bag content info: " + err.Error())
409+
}
410+
411+
contentInfos := make([]contentInfo, 2)
412+
contentInfos[0] = *shroudedKeyContentInfo
413+
contentInfos[1] = *certBagContentInfo
414+
415+
return contentInfos, nil
416+
}
417+
384418
func makeSalt(saltByteCount int) ([]byte, error) {
385419
salt := make([]byte, saltByteCount)
386420
_, err := io.ReadFull(rand.Reader, salt)
@@ -453,6 +487,76 @@ func Encode(derBytes []byte, privateKey interface{}, password string) (pfxBytes
453487
return bytes, err
454488
}
455489

490+
// EncodeModern converts a certificate and a private key to the PKCS#12 byte stream format
491+
// using AES-256-CBC encryption with PBES2 (PKCS#5 v2.0).
492+
// This provides modern, standards-compliant encryption that is fully compatible with
493+
// Windows and Azure while offering much stronger security than the legacy Triple DES.
494+
//
495+
// derBytes is a DER encoded certificate.
496+
// privateKey is an RSA or ECDSA private key.
497+
// password is the password to encrypt the private key.
498+
func EncodeModern(derBytes []byte, privateKey interface{}, password string) (pfxBytes []byte, err error) {
499+
secret, err := bmpString(password)
500+
if err != nil {
501+
return nil, ErrIncorrectPassword
502+
}
503+
504+
contentInfos, err := makeContentInfosModern(derBytes, privateKey, secret)
505+
if err != nil {
506+
return nil, err
507+
}
508+
509+
// Marshal []contentInfo so we can re-constitute the byte stream that will
510+
// be suitable for computing the MAC
511+
bytes, err := asn1.Marshal(contentInfos)
512+
if err != nil {
513+
return nil, err
514+
}
515+
516+
// Unmarshal as an asn1.RawValue so, we can compute the MAC against the .Bytes
517+
var contentInfosRaw asn1.RawValue
518+
err = unmarshal(bytes, &contentInfosRaw)
519+
if err != nil {
520+
return nil, err
521+
}
522+
523+
authSafeContentInfo, err := makeContentInfo(contentInfosRaw)
524+
if err != nil {
525+
return nil, EncodeError("authSafe content info: " + err.Error())
526+
}
527+
528+
salt, err := makeSalt(pbeSaltSizeBytes)
529+
if err != nil {
530+
return nil, EncodeError("salt value: " + err.Error())
531+
}
532+
533+
// Compute the MAC for marshaled bytes of contentInfos, which includes the
534+
// cert bag, and the shrouded key bag.
535+
digest := computeMac(contentInfosRaw.FullBytes, pbeIterationCount, salt, secret)
536+
537+
pfx := pfxPdu{
538+
Version: 3,
539+
AuthSafe: *authSafeContentInfo,
540+
MacData: macData{
541+
Iterations: pbeIterationCount,
542+
MacSalt: salt,
543+
Mac: digestInfo{
544+
Algorithm: pkix.AlgorithmIdentifier{
545+
Algorithm: oidSHA1,
546+
},
547+
Digest: digest,
548+
},
549+
},
550+
}
551+
552+
bytes, err = asn1.Marshal(pfx)
553+
if err != nil {
554+
return nil, EncodeError("marshal PFX PDU: " + err.Error())
555+
}
556+
557+
return bytes, err
558+
}
559+
456560
func getSafeContents(p12Data, password []byte) (bags []safeBag, updatedPassword []byte, err error) {
457561
pfx := new(pfxPdu)
458562

0 commit comments

Comments
 (0)