go/mqtt/auth_utils.go (84 lines of code) (raw):

// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. package mqtt import ( "crypto/aes" "crypto/cipher" "crypto/tls" "crypto/x509" "encoding/pem" "errors" "os" "golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/sha3" ) // loadCACertPool loads a CA certificate pool from the specified file. func loadCACertPool(caFile string) (*x509.CertPool, error) { caCert, err := os.ReadFile(caFile) if err != nil { return nil, err } caCertPool := x509.NewCertPool() if !caCertPool.AppendCertsFromPEM(caCert) { return nil, err } return caCertPool, nil } // decryptPEMBlock decrypts a PEM block using PBKDF2 and AES-GCM. func decryptPEMBlock(block *pem.Block, password []byte) ([]byte, error) { if block == nil { return nil, errors.New("PEM block is nil") } // Extract the salt (first 8 bytes). salt := block.Bytes[:8] // Derive key using PBKDF2. key := pbkdf2.Key(password, salt, 10000, 32, sha3.New256) // Decrypt the block using AES-GCM. return aesGCMDecrypt(block.Bytes[8:], key) } // aesGCMDecrypt decrypts data using AES-GCM mode. func aesGCMDecrypt(encrypted, key []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { return nil, err } // AES-GCM typically uses a 12-byte(96-bit) nonce(random number). This size // is recommended because it provides a good balance between security and // performance. if len(encrypted) < aesGcmNonce { return nil, errors.New("ciphertext in PEM block is too short") } nonce, ciphertext := encrypted[:12], encrypted[12:] gcm, err := cipher.NewGCM(block) if err != nil { return nil, err } return gcm.Open(nil, nonce, ciphertext, nil) } // loadX509KeyPairWithPassword loads key pair from the encrypted file. func loadX509KeyPairWithPassword( certFile, keyFile, passFile string, ) (tls.Certificate, error) { certPEMBlock, err := os.ReadFile(certFile) if err != nil { return tls.Certificate{}, err } keyPEMBlock, err := os.ReadFile(keyFile) if err != nil { return tls.Certificate{}, err } password, err := os.ReadFile(passFile) if err != nil { return tls.Certificate{}, err } keyDERBlock, _ := pem.Decode(keyPEMBlock) if keyDERBlock == nil { return tls.Certificate{}, errors.New( "failed to decode PEM block containing private key", ) } // x509.DecryptPEMBlock is deprecated due to insecurity, and x509 library // doesn't want to support it: https://github.com/golang/go/issues/8860 decryptedDERBlock, err := decryptPEMBlock(keyDERBlock, password) if err != nil { return tls.Certificate{}, err } decryptedPEMBlock := pem.Block{ Type: keyDERBlock.Type, Bytes: decryptedDERBlock, } keyPEM := pem.EncodeToMemory(&decryptedPEMBlock) cert, err := tls.X509KeyPair(certPEMBlock, keyPEM) if err != nil { return tls.Certificate{}, err } return cert, nil }