1
1
<?php
2
2
3
3
namespace DotenvVault ;
4
+ use Dotenv \Dotenv ;
5
+ use Dotenv \Loader \Loader ;
6
+ use Dotenv \Loader \LoaderInterface ;
7
+ use Dotenv \Parser \Parser ;
8
+ use Dotenv \Parser \ParserInterface ;
9
+ use Dotenv \Repository \RepositoryBuilder ;
10
+ use Dotenv \Repository \RepositoryInterface ;
11
+ use Dotenv \Store \StoreBuilder ;
12
+ use Dotenv \Store \StoreInterface ;
13
+ use Exception ;
4
14
5
- class DotenvVault{
15
+ class DotEnvVaultError extends Exception { }
16
+
17
+ class DotEnvVault extends Dotenv
18
+ {
19
+ private $ store ;
20
+ private $ parser ;
21
+ private $ loader ;
22
+ private $ repository ;
23
+ private $ dotenv_key ;
24
+ public function __construct (
25
+ StoreInterface $ store ,
26
+ ParserInterface $ parser ,
27
+ LoaderInterface $ loader ,
28
+ RepositoryInterface $ repository
29
+ )
30
+ {
31
+ $ this ->store = $ store ;
32
+ $ this ->parser = $ parser ;
33
+ $ this ->loader = $ loader ;
34
+ $ this ->repository = $ repository ;
35
+ }
36
+
37
+ public function load ()
38
+ {
39
+ $ this ->dotenv_key = getenv ("DOTENV_KEY " );
40
+ if ($ this ->dotenv_key !== false ){
41
+
42
+ $ entries = $ this ->parser ->parse ($ this ->store ->read ());
43
+ $ this ->loader ->load ($ this ->repository , $ entries );
44
+
45
+ $ plaintext = $ this ->parse_vault ();
46
+
47
+ // parsing plaintext and loading to $_ENV
48
+ $ test_entries = $ this ->parser ->parse ($ plaintext );
49
+ $ this ->loader ->load ($ this ->repository , $ test_entries );
50
+ }
51
+ else {
52
+ $ entries = $ this ->parser ->parse ($ this ->store ->read ());
53
+
54
+ var_dump ($ entries [0 ]->getName ());
55
+ var_dump ($ entries [0 ]->getValue ());
56
+
57
+ return $ this ->loader ->load ($ this ->repository , $ entries );
58
+ }
59
+ }
60
+
61
+ public function parse_vault ()
62
+ {
63
+ $ dotenv_keys = explode (', ' , $ this ->dotenv_key );
64
+ $ keys = array ();
65
+ foreach ($ dotenv_keys as $ key )
66
+ {
67
+ // parse DOTENV_KEY, format is a URI.
68
+ $ uri = parse_url (trim ($ key ));
69
+
70
+ // get encrypted key
71
+ $ pass = $ uri ['pass ' ];
72
+
73
+ // Get environment from query params.
74
+ parse_str ($ uri ['query ' ], $ params );
75
+ $ vault_environment = $ params ['environment ' ] or throw new DotEnvVaultError ('INVALID_DOTENV_KEY: Missing environment part. ' );
76
+
77
+ # Getting ciphertext from correct environment in .env.vault
78
+ $ vault_environment = strtoupper ($ vault_environment );
79
+ $ environment_key = "DOTENV_VAULT_ {$ vault_environment }" ;
80
+
81
+ $ ciphertext = $ _ENV ["{$ environment_key }" ] or throw new DotEnvVaultError ("NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment {$ environment_key } in your .env.vault file. Run 'npx dotenv-vault build' to include it. " );
82
+
83
+ array_push ($ keys , array ('encrypted_key ' => $ pass , 'ciphertext ' => $ ciphertext ));
84
+ }
85
+ return $ this ->key_rotation ($ keys );
86
+ }
87
+
88
+ private function key_rotation ($ keys ){
89
+ $ count = count ($ keys );
90
+ foreach ($ keys as $ index =>$ value ) {
91
+ $ decrypt = $ this ->decrypt ($ value ['ciphertext ' ], $ value ['encrypted_key ' ]);
92
+
93
+ if ($ decrypt == false && $ index + 1 >= $ count ){
94
+ throw new DotEnvVaultError ('INVALID_DOTENV_KEY: Key must be valid. ' );
95
+ }
96
+ elseif ($ decrypt == false ){
97
+ continue ;
98
+ }
99
+ else {
100
+ return $ decrypt ;
101
+ }
102
+ }
103
+ }
104
+
105
+ private function decrypt ($ data , $ secret )
106
+ {
107
+ $ secret = hex2bin (substr ($ secret , 4 , strlen ($ secret )));
108
+ $ data = base64_decode ($ data , true );
109
+ $ nonce = substr ($ data , 0 , 12 );
110
+ $ tag = substr ($ data , -16 );
111
+ $ ciphertext = substr ($ data , 12 , -16 );
6
112
7
- }
113
+ try {
114
+ return openssl_decrypt (
115
+ $ ciphertext ,
116
+ 'aes-256-gcm ' ,
117
+ $ secret ,
118
+ OPENSSL_RAW_DATA ,
119
+ $ nonce ,
120
+ $ tag
121
+ );
122
+ } catch (Exception $ e ) {
123
+ return false ;
124
+ }
125
+ }
126
+
127
+ public static function createImmutable ($ paths , $ names = null , bool $ shortCircuit = true , string $ fileEncoding = null )
128
+ {
129
+ $ repository = RepositoryBuilder::createWithDefaultAdapters ()->immutable ()->make ();
130
+
131
+ return self ::create ($ repository , $ paths , $ names , $ shortCircuit , $ fileEncoding );
132
+ }
133
+
134
+ public static function create (RepositoryInterface $ repository , $ paths , $ names = null , bool $ shortCircuit = true , string $ fileEncoding = null )
135
+ {
136
+ $ builder = $ names === null ? StoreBuilder::createWithDefaultName () : StoreBuilder::createWithNoNames ();
137
+
138
+ foreach ((array ) $ paths as $ path ) {
139
+ $ builder = $ builder ->addPath ($ path );
140
+ }
141
+
142
+ foreach ((array ) $ names as $ name ) {
143
+ $ builder = $ builder ->addName ($ name );
144
+ }
145
+
146
+ if ($ shortCircuit ) {
147
+ $ builder = $ builder ->shortCircuit ();
148
+ }
149
+
150
+ return new self ($ builder ->fileEncoding ($ fileEncoding )->make (), new Parser (), new Loader (), $ repository );
151
+ }
152
+ }
0 commit comments