github.com/mholt/caddy-l4@v0.0.0-20241104153248-ec8fae209322/modules/l4openvpn/crypto.go (about)

     1  // Copyright 2024 VNXME
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package l4openvpn
    16  
    17  import (
    18  	"crypto/aes"
    19  	"crypto/cipher"
    20  	"crypto/hmac"
    21  	"crypto/md5"
    22  	"crypto/sha1"
    23  	"crypto/sha256"
    24  	"crypto/sha512"
    25  	"encoding/base64"
    26  	"encoding/hex"
    27  	"errors"
    28  	"golang.org/x/crypto/blake2b"
    29  	"golang.org/x/crypto/blake2s"
    30  	"golang.org/x/crypto/ripemd160"
    31  	"golang.org/x/crypto/sha3"
    32  	"hash"
    33  	"io"
    34  	"os"
    35  	"regexp"
    36  	"slices"
    37  	"strings"
    38  )
    39  
    40  // AuthDigest represents a digest used for computing HMACs of control messages.
    41  type AuthDigest struct {
    42  	// Creator is a function returning something that implements hash.Hash interface.
    43  	// Creator is required whenever Generator is nil.
    44  	Creator func() hash.Hash
    45  	// Generator is a function returning an HMAC for a given set of key and plain bytes.
    46  	// Generator is optional, but it takes precedence over Creator.
    47  	Generator func(key, plain []byte) []byte
    48  	// Names contains a list of digest names in various notations.
    49  	Names []string
    50  	// Size is a number of bytes all computed HMACs will have.
    51  	Size int
    52  }
    53  
    54  // HMACGenerateOnClient computes an HMAC of a given plain text with a part of a given StaticKey
    55  // (to be sent by the client to the server).
    56  func (ad *AuthDigest) HMACGenerateOnClient(sk *StaticKey, plain []byte) []byte {
    57  	key := sk.GetClientAuthKey(ad.Size)
    58  	if ad.Generator != nil {
    59  		return ad.Generator(key, plain)
    60  	}
    61  	return HMACCreateAndGenerate(ad.Creator, key, plain)
    62  }
    63  
    64  // HMACGenerateOnServer computes an HMAC of a given plain text with a part of a given StaticKey
    65  // (to be sent by the server to the client).
    66  func (ad *AuthDigest) HMACGenerateOnServer(sk *StaticKey, plain []byte) []byte {
    67  	key := sk.GetServerAuthKey(ad.Size)
    68  	if ad.Generator != nil {
    69  		return ad.Generator(key, plain)
    70  	}
    71  	return HMACCreateAndGenerate(ad.Creator, key, plain)
    72  }
    73  
    74  // HMACValidateOnClient compares an expected HMAC (as received by the client from the server)
    75  // with an actual HMAC of a given plain text it computes with a part of a given StaticKey.
    76  func (ad *AuthDigest) HMACValidateOnClient(sk *StaticKey, plain, expected []byte) bool {
    77  	actual := ad.HMACGenerateOnServer(sk, plain)
    78  	return hmac.Equal(actual, expected)
    79  }
    80  
    81  // HMACValidateOnServer compares an expected HMAC (as received by the server from the client)
    82  // with an actual HMAC of a given plain text it computes with a part of a given StaticKey.
    83  func (ad *AuthDigest) HMACValidateOnServer(sk *StaticKey, plain, expected []byte) bool {
    84  	actual := ad.HMACGenerateOnClient(sk, plain)
    85  	return hmac.Equal(actual, expected)
    86  }
    87  
    88  // CryptCipher represents a cipher used for en/decrypting control messages.
    89  type CryptCipher struct {
    90  	// Decryptor is a function returning plain bytes from encrypted ones.
    91  	Decryptor func(key, iv, encrypted []byte) []byte
    92  	// Encryptor is a function returning encrypted bytes from plain ones.
    93  	Encryptor func(key, iv, plain []byte) []byte
    94  	// Names contains a list of cipher names in various notations.
    95  	Names []string
    96  	// SizeBlock is a number of bytes an initialization vector (IV) must have.
    97  	SizeBlock int
    98  	// SizeKey is a number of bytes an en/decryption key must have.
    99  	SizeKey int
   100  }
   101  
   102  // DecryptOnClient decrypts given encrypted bytes with a part of a given StaticKey
   103  // (as received by the client from the server).
   104  func (cc *CryptCipher) DecryptOnClient(sk *StaticKey, iv, encrypted []byte) []byte {
   105  	key := sk.GetClientDecryptKey(cc.SizeKey)
   106  	return cc.Decryptor(key, iv, encrypted)
   107  }
   108  
   109  // DecryptOnServer decrypts given encrypted bytes with a part of a given StaticKey
   110  // (as received by the server from the client).
   111  func (cc *CryptCipher) DecryptOnServer(sk *StaticKey, iv, encrypted []byte) []byte {
   112  	key := sk.GetServerDecryptKey(cc.SizeKey)
   113  	return cc.Decryptor(key, iv, encrypted)
   114  }
   115  
   116  // EncryptOnClient encrypts given plain bytes with a part of a given StaticKey
   117  // (to be sent by the client to the server).
   118  func (cc *CryptCipher) EncryptOnClient(sk *StaticKey, iv, plain []byte) []byte {
   119  	key := sk.GetClientEncryptKey(cc.SizeKey)
   120  	return cc.Encryptor(key, iv, plain)
   121  }
   122  
   123  // EncryptOnServer encrypts given plain bytes with a part of a given StaticKey
   124  // (to be sent by the server to the client).
   125  func (cc *CryptCipher) EncryptOnServer(sk *StaticKey, iv, plain []byte) []byte {
   126  	key := sk.GetServerEncryptKey(cc.SizeKey)
   127  	return cc.Encryptor(key, iv, plain)
   128  }
   129  
   130  // StaticKey is an OpenVPN static key used for authentication and encryption of control messages.
   131  //
   132  // Notes:
   133  //
   134  // Authentication. If no key direction is set (i.e. a bidirectional key), OpenVPN uses key[64:64+size] for
   135  // computing and validating HMACs on both the client and the server. If the client has `key-direction 1`
   136  // and the server has `key-direction 0`, OpenVPN uses key[192:192+size] for computing HMACs on the client and
   137  // validating them on the server. If the client has `key-direction 0` and the server has `key-direction 1`
   138  // (i.e. an inverse direction in violation of the recommendations), OpenVPN uses key[64:64+size] for computing
   139  // HMACs on the client and validating them on the server. Inverse and Bidi are mutually exclusive. If both are
   140  // set, Bidi takes precedence.
   141  //
   142  // En/decryption. OpenVPN always takes 2 different keys for encryption and decryption, so Bidi is completely
   143  // ignored. Unless Inverse is set, key[128:128+size] is used for encryption on the client and key[0:0+size] is
   144  // used for decryption on the client with the server applying these keys in the other way.
   145  type StaticKey struct {
   146  	// Bidi mimics `key-direction` omitted for both the server and the client.
   147  	Bidi bool
   148  	// Inverse mimics `key-direction 1` set for the server and `key-direction 0` set for the client.
   149  	Inverse bool
   150  	// KeyBytes must contain 128 or 256 bytes of the static key.
   151  	KeyBytes []byte
   152  }
   153  
   154  // FromBase64 fills sk's KeyBytes from a given base64 string.
   155  func (sk *StaticKey) FromBase64(s string) (err error) {
   156  	sk.KeyBytes, err = base64.StdEncoding.DecodeString(s)
   157  	return
   158  }
   159  
   160  // FromGroupKeyFile fills sk's KeyBytes from a given group key file.
   161  func (sk *StaticKey) FromGroupKeyFile(path string) error {
   162  	return sk.FromFile(path, StaticKeyFromFileHex, StaticKeyBytesTotal*2, sk.FromHex)
   163  }
   164  
   165  // FromServerKeyFile fills sk's KeyBytes from a given server key file.
   166  func (sk *StaticKey) FromServerKeyFile(path string) error {
   167  	return sk.FromFile(path, StaticKeyFromFileBase64, base64.StdEncoding.EncodedLen(StaticKeyBytesHalf), sk.FromBase64)
   168  }
   169  
   170  // FromFile fills sk's KeyBytes from a given file.
   171  func (sk *StaticKey) FromFile(path string, re *regexp.Regexp, size int, from func(string) error) error {
   172  	file, err := os.Open(path)
   173  	if err != nil {
   174  		return err
   175  	}
   176  	defer func() { _ = file.Close() }()
   177  
   178  	n := 1024
   179  	buf := make([]byte, n)
   180  	n, err = file.Read(buf)
   181  	if err != nil && !errors.Is(err, io.EOF) {
   182  		return err
   183  	}
   184  
   185  	if n > 0 {
   186  		var s string
   187  		if r := re.FindStringSubmatch(string(buf[:n])); r != nil && len(r) == 2 {
   188  			s = strings.ReplaceAll(r[1], "\r", "")
   189  			s = strings.ReplaceAll(s, "\n", "")
   190  			if size == 0 || len(s) == size {
   191  				return from(s)
   192  			}
   193  		}
   194  	}
   195  
   196  	return ErrInvalidStaticKeyFileContents
   197  }
   198  
   199  // FromHex fills sk's KeyBytes from a given hex string.
   200  func (sk *StaticKey) FromHex(s string) (err error) {
   201  	sk.KeyBytes, err = hex.DecodeString(s)
   202  	return
   203  }
   204  
   205  // GetClientAuthBytes returns a quarter of KeyBytes to be used for authentication of control messages
   206  // composed by the client.
   207  func (sk *StaticKey) GetClientAuthBytes() []byte {
   208  	if sk.Inverse || sk.Bidi {
   209  		return sk.GetQuarterBytes(1)
   210  	}
   211  	return sk.GetQuarterBytes(3)
   212  }
   213  
   214  // GetClientAuthKey returns a key of a given size from GetClientAuthBytes.
   215  func (sk *StaticKey) GetClientAuthKey(size int) []byte {
   216  	return sk.GetClientAuthBytes()[:min(size, StaticKeyBytesQuarter)]
   217  }
   218  
   219  // GetClientEncryptBytes returns a quarter of KeyBytes to be used for encryption of control messages
   220  // composed by the client.
   221  func (sk *StaticKey) GetClientEncryptBytes() []byte {
   222  	if sk.Inverse {
   223  		return sk.GetQuarterBytes(0)
   224  	}
   225  	return sk.GetQuarterBytes(2)
   226  }
   227  
   228  // GetClientEncryptKey returns a key of a given size from GetClientEncryptBytes.
   229  func (sk *StaticKey) GetClientEncryptKey(size int) []byte {
   230  	return sk.GetClientEncryptBytes()[:min(size, StaticKeyBytesQuarter)]
   231  }
   232  
   233  // GetClientDecryptBytes returns a quarter of KeyBytes to be used for decryption of control messages
   234  // received by the client.
   235  func (sk *StaticKey) GetClientDecryptBytes() []byte {
   236  	if sk.Inverse {
   237  		return sk.GetQuarterBytes(2)
   238  	}
   239  	return sk.GetQuarterBytes(0)
   240  }
   241  
   242  // GetClientDecryptKey returns a key of a given size from GetClientDecryptBytes.
   243  func (sk *StaticKey) GetClientDecryptKey(size int) []byte {
   244  	return sk.GetClientDecryptBytes()[:min(size, StaticKeyBytesQuarter)]
   245  }
   246  
   247  // GetServerAuthBytes returns a quarter of KeyBytes to be used for authentication of control messages
   248  // composed by the server.
   249  func (sk *StaticKey) GetServerAuthBytes() []byte {
   250  	if sk.Inverse && !sk.Bidi {
   251  		return sk.GetQuarterBytes(3)
   252  	}
   253  	return sk.GetQuarterBytes(1)
   254  }
   255  
   256  // GetServerAuthKey returns a key of a given size from GetServerAuthKey.
   257  func (sk *StaticKey) GetServerAuthKey(size int) []byte {
   258  	return sk.GetServerAuthBytes()[:min(size, StaticKeyBytesQuarter)]
   259  }
   260  
   261  // GetServerEncryptBytes returns a quarter of KeyBytes to be used for encryption of control messages
   262  // composed by the server.
   263  func (sk *StaticKey) GetServerEncryptBytes() []byte {
   264  	return sk.GetClientDecryptBytes()
   265  }
   266  
   267  // GetServerEncryptKey returns a key of a given size from GetServerEncryptBytes.
   268  func (sk *StaticKey) GetServerEncryptKey(size int) []byte {
   269  	return sk.GetClientDecryptKey(size)
   270  }
   271  
   272  // GetServerDecryptBytes returns a quarter of KeyBytes to be used for decryption of control messages
   273  // received by the server.
   274  func (sk *StaticKey) GetServerDecryptBytes() []byte {
   275  	return sk.GetClientEncryptBytes()
   276  }
   277  
   278  // GetServerDecryptKey returns a key of a given size from GetServerDecryptBytes.
   279  func (sk *StaticKey) GetServerDecryptKey(size int) []byte {
   280  	return sk.GetClientEncryptKey(size)
   281  }
   282  
   283  // GetQuarterBytes returns a nth (0-based) quarter of KeyBytes
   284  func (sk *StaticKey) GetQuarterBytes(q uint) []byte {
   285  	q = q % 4
   286  	if len(sk.KeyBytes) < StaticKeyBytesTotal {
   287  		q = q % 2
   288  	}
   289  	if len(sk.KeyBytes) < StaticKeyBytesHalf {
   290  		q = 0
   291  	}
   292  	if len(sk.KeyBytes) < StaticKeyBytesQuarter {
   293  		return sk.KeyBytes
   294  	}
   295  	return sk.KeyBytes[q*StaticKeyBytesQuarter : (q+1)*StaticKeyBytesQuarter]
   296  }
   297  
   298  // ToBase64 returns a base64 string representing KeyBytes.
   299  func (sk *StaticKey) ToBase64() string {
   300  	return base64.StdEncoding.EncodeToString(sk.KeyBytes)
   301  }
   302  
   303  // ToHex returns a hex string representing KeyBytes.
   304  func (sk *StaticKey) ToHex() string {
   305  	return hex.EncodeToString(sk.KeyBytes)
   306  }
   307  
   308  // AuthDigests contains all the supported items of AuthDigest type.
   309  var AuthDigests = []*AuthDigest{
   310  	// Legacy digests
   311  	{Creator: md5.New, Names: []string{"MD5", "SSL3-MD5", "md5", "ssl3-md5"}, Size: md5.Size},
   312  	{Creator: sha1.New, Names: []string{"SHA-1", "SHA1", "SSL3-SHA1", "sha-1", "sha1", "ssl3-sha1"}, Size: sha1.Size},
   313  	{Creator: ripemd160.New, Names: []string{"RIPEMD-160", "RMD-160", "RIPEMD160", "RIPEMD", "RMD160", "ripemd-160", "rmd-160", "ripemd160", "ripemd", "rmd160"}, Size: ripemd160.Size},
   314  	// SHA2 digests
   315  	{Creator: sha256.New224, Names: []string{"SHA-224", "SHA2-224", "SHA224", "sha-224", "sha2-224", "sha224"}, Size: sha256.Size224},
   316  	{Creator: sha256.New, Names: []string{"SHA-256", "SHA2-256", "SHA256", "sha-256", "sha2-256", "sha256"}, Size: sha256.Size},
   317  	{Creator: sha512.New384, Names: []string{"SHA-384", "SHA2-384", "SHA384", "sha-384", "sha2-384", "sha384"}, Size: sha512.Size384},
   318  	{Creator: sha512.New, Names: []string{"SHA-512", "SHA2-512", "SHA512", "sha-512", "sha2-512", "sha512"}, Size: sha512.Size},
   319  	{Creator: sha512.New512_224, Names: []string{"SHA-512/224", "SHA2-512/224", "SHA512-224", "sha-512/224", "sha2-512/224", "sha512-224"}, Size: sha512.Size224},
   320  	{Creator: sha512.New512_256, Names: []string{"SHA-512/256", "SHA2-512/256", "SHA512-256", "sha-512/256", "sha2-512/256", "sha512-256"}, Size: sha512.Size256},
   321  	// SHA3 digests
   322  	{Creator: sha3.New224, Names: []string{"SHA3-224", "sha3-224"}, Size: 28},
   323  	{Creator: sha3.New256, Names: []string{"SHA3-256", "sha3-256"}, Size: 32},
   324  	{Creator: sha3.New384, Names: []string{"SHA3-384", "sha3-384"}, Size: 48},
   325  	{Creator: sha3.New512, Names: []string{"SHA3-512", "sha3-512"}, Size: 64},
   326  	// BLAKE digests
   327  	{
   328  		Creator: func() hash.Hash {
   329  			h, _ := blake2s.New256(nil)
   330  			return h
   331  		},
   332  		Names: []string{"BLAKE2s-256", "BLAKE2S-256", "blake2s-256", "blake2S-256"},
   333  		Size:  blake2s.Size,
   334  	},
   335  	{
   336  		Creator: func() hash.Hash {
   337  			h, _ := blake2b.New512(nil)
   338  			return h
   339  		},
   340  		Names: []string{"BLAKE2b-512", "BLAKE2B-512", "blake2b-512", "blake2B-512"},
   341  		Size:  blake2b.Size,
   342  	},
   343  	// SHAKE digests
   344  	{
   345  		Creator: func() hash.Hash {
   346  			return sha3.NewShake128()
   347  		},
   348  		Names: []string{"SHAKE-128", "SHAKE128", "shake-128", "shake128"},
   349  		Size:  32,
   350  	},
   351  	{
   352  		Creator: func() hash.Hash {
   353  			return sha3.NewShake256()
   354  		},
   355  		Names: []string{"SHAKE-256", "SHAKE256", "shake-256", "shake256"},
   356  		Size:  64,
   357  	},
   358  	// Custom digests
   359  	{
   360  		// This MD5-SHA1 implementation outputs bytes that match many online generators. However,
   361  		// its output never matches the HMACs of the sample packets generated on Windows and macOS.
   362  		// Since OpenVPN uses the OpenSSL library under the hood, the reason for this hash mismatch
   363  		// should most likely be traced there. Assuming the MD5-SHA1 digest has a very limited use,
   364  		// there is little probability anyone will ever face this issue.
   365  		Generator: func(key, plain []byte) []byte {
   366  			hmacMD5 := hmac.New(md5.New, key[:md5.Size])
   367  			hmacMD5.Write(plain)
   368  			hmacSHA1 := hmac.New(sha1.New, key[:sha1.Size])
   369  			hmacSHA1.Write(plain)
   370  			result := make([]byte, 0, md5.Size+sha1.Size)
   371  			return hmacSHA1.Sum(hmacMD5.Sum(result))
   372  		},
   373  		Names: []string{"MD5+SHA1", "MD5-SHA1", "MD5SHA1", "md5+sha1", "md5-sha1", "md5sha1"},
   374  		Size:  md5.Size + sha1.Size,
   375  	},
   376  }
   377  
   378  // AuthDigestSizes contains sizes of all the supported items of AuthDigest type.
   379  var AuthDigestSizes = func() []int {
   380  	presence := make([]bool, AuthHMACBytesMax+1)
   381  	n := 0
   382  	for _, ad := range AuthDigests {
   383  		if !presence[ad.Size] {
   384  			n++
   385  			presence[ad.Size] = true
   386  		}
   387  	}
   388  	sizes := make([]int, 0, n)
   389  	for i, present := range presence {
   390  		if present {
   391  			sizes = append(sizes, i)
   392  		}
   393  	}
   394  	return sizes
   395  }()
   396  
   397  // CryptCiphers contains all the supported items of CryptCipher type.
   398  var CryptCiphers = []*CryptCipher{
   399  	{
   400  		Decryptor: func(key, iv, encrypted []byte) []byte {
   401  			plain := make([]byte, len(encrypted))
   402  			block, _ := aes.NewCipher(key)
   403  			ctr := cipher.NewCTR(block, iv)
   404  			ctr.XORKeyStream(plain, encrypted)
   405  			return plain
   406  		},
   407  		Encryptor: func(key, iv, plain []byte) []byte {
   408  			encrypted := make([]byte, len(plain))
   409  			block, _ := aes.NewCipher(key)
   410  			ctr := cipher.NewCTR(block, iv)
   411  			ctr.XORKeyStream(encrypted, plain)
   412  			return encrypted
   413  		},
   414  		Names:     []string{"AES-256-CTR", "aes-256-ctr"},
   415  		SizeBlock: 16,
   416  		SizeKey:   32,
   417  	},
   418  }
   419  
   420  // AuthDigestDefault is the default AuthDigest used in the crypt and crypt2 modes (SHA-256).
   421  var AuthDigestDefault = AuthDigestFindByName("SHA-256")
   422  
   423  // CryptCipherDefault is the default CryptCipher used in the crypt and crypt2 modes (AES-256-CTR).
   424  var CryptCipherDefault = CryptCipherFindByName("AES-256-CTR")
   425  
   426  var StaticKeyFromFileBase64 = regexp.MustCompile("^(?:#.*?\\r?\\n)*" +
   427  	"-----BEGIN OpenVPN tls-crypt-v2 (?:client|server) key-----\\r?\\n" +
   428  	"([0-9a-zA-Z+=\\/\\r\\n]+)" +
   429  	"-----END OpenVPN tls-crypt-v2 (?:client|server) key-----(?:\\r?\\n)?$")
   430  var StaticKeyFromFileHex = regexp.MustCompile("^(?:#.*?\\r?\\n)*" +
   431  	"-----BEGIN OpenVPN Static key V1-----\\r?\\n" +
   432  	"([0-9a-fA-F\\r\\n]+)" +
   433  	"-----END OpenVPN Static key V1-----(?:\\r?\\n)?$")
   434  
   435  var (
   436  	ErrInvalidStaticKeyFileContents = errors.New("invalid static key file contents")
   437  )
   438  
   439  const (
   440  	AuthHMACBytesMax = sha512.Size
   441  	AuthHMACBytesMin = md5.Size
   442  
   443  	CryptHMACBytesTotal = sha256.Size
   444  
   445  	StaticKeyBytesTotal   = 256
   446  	StaticKeyBytesHalf    = StaticKeyBytesTotal / 2
   447  	StaticKeyBytesQuarter = StaticKeyBytesTotal / 4
   448  )
   449  
   450  // AuthDigestFindByName returns a pointer to AuthDigest having a given name or nil.
   451  func AuthDigestFindByName(name string) *AuthDigest {
   452  	for _, ad := range AuthDigests {
   453  		if slices.Contains(ad.Names, name) {
   454  			return ad
   455  		}
   456  	}
   457  	return nil
   458  }
   459  
   460  // CryptCipherFindByName returns a pointer to CryptCipher having a given name or nil.
   461  func CryptCipherFindByName(name string) *CryptCipher {
   462  	for _, cc := range CryptCiphers {
   463  		if slices.Contains(cc.Names, name) {
   464  			return cc
   465  		}
   466  	}
   467  	return nil
   468  }
   469  
   470  // HMACCreateAndGenerate uses a given digest creator to compute an HMAC of a given plain text with a given key.
   471  func HMACCreateAndGenerate(creator func() hash.Hash, key, plain []byte) []byte {
   472  	hmacDigest := hmac.New(creator, key)
   473  	hmacDigest.Write(plain)
   474  	return hmacDigest.Sum(nil)
   475  }
   476  
   477  // StaticKeyNewFromBase64 returns a pointer to StaticKey filled with bytes from a given base64 string.
   478  func StaticKeyNewFromBase64(s string, inverse bool, bidi bool) *StaticKey {
   479  	sk := &StaticKey{Inverse: inverse, Bidi: bidi}
   480  	_ = sk.FromBase64(s)
   481  	return sk
   482  }
   483  
   484  // StaticKeyNewFromHex returns a pointer to StaticKey filled with bytes from a given hex string.
   485  func StaticKeyNewFromHex(s string, inverse bool, bidi bool) *StaticKey {
   486  	sk := &StaticKey{Inverse: inverse, Bidi: bidi}
   487  	_ = sk.FromHex(s)
   488  	return sk
   489  }