-
Notifications
You must be signed in to change notification settings - Fork 51
/
sign.go
222 lines (189 loc) · 6.47 KB
/
sign.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
// Copyright (c) Edgeless Systems GmbH.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
package cli
import (
"encoding/json"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"strconv"
"github.com/edgelesssys/ego/ego/config"
)
const (
defaultConfigFilename = "enclave.json"
defaultPrivKeyFilename = "private.pem"
defaultPubKeyFilename = "public.pem"
)
// ErrNoOEInfo defines an error when no .oeinfo section could be found. This likely occurs when the binary to sign was not built with ego-go.
var ErrNoOEInfo = errors.New("could not find .oeinfo section")
// errConfigDoesNotExist defines an error when enclave.json or the expected .json file from user input does not exist. It is only used for internal error handling.
var errConfigDoesNotExist = errors.New("enclave config file not found")
func (c *Cli) signWithJSON(conf *config.Config) error {
symbols, err := c.getSymbolsFromELF(conf.Exe)
if err != nil {
return fmt.Errorf("getting symbols: %w", err)
}
// First, check if the executable does not contain unsupported imports / symbols.
if err := checkUnsupportedImports(symbols); err != nil {
return err
}
// Check that heapSize is in the supported range of the heap mode the binary was built with.
if err := checkHeapMode(symbols, conf.HeapSize); err != nil {
return err
}
// write temp .conf file
cProduct := "ProductID=" + strconv.Itoa(conf.ProductID) + "\n"
cSecurityVersion := "SecurityVersion=" + strconv.Itoa(conf.SecurityVersion) + "\n"
var cDebug string
if conf.Debug {
cDebug = "Debug=1\n"
} else {
cDebug = "Debug=0\n"
}
// calculate number of pages: HeapSize[MiB], pageSize is 4096B
heapPages := conf.HeapSize * 1024 * 1024 / 4096
cNumHeapPages := "NumHeapPages=" + strconv.Itoa(heapPages) + "\n"
cStackPages := "NumStackPages=1024\n"
cNumTCS := "NumTCS=32\n"
var cExecutableHeap string
if conf.ExecutableHeap {
cExecutableHeap = "ExecutableHeap=1\n"
}
file, err := c.fs.TempFile("", "")
if err != nil {
return err
}
defer func() { _ = c.fs.Remove(file.Name()) }()
_, err = file.Write([]byte(cProduct + cSecurityVersion + cDebug + cNumHeapPages + cStackPages + cNumTCS + cExecutableHeap))
if err != nil {
return err
}
if err := file.Close(); err != nil {
return err
}
// Prepare JSON data for embedding to the executable
jsonData, err := json.Marshal(conf)
if err != nil {
return err
}
// Embed enclave.json inside executable as payload
if err := c.embedConfigAsPayload(conf.Exe, jsonData); err != nil {
return err
}
// create public and private key if private key does not exist
c.createDefaultKeypair(conf.Key)
enclavePath := filepath.Join(c.egoPath, "share", "ego-enclave")
cmd := exec.Command(c.getOesignPath(), "sign", "-e", enclavePath, "-c", file.Name(), "-k", conf.Key, "--payload", conf.Exe)
out, err := c.runner.CombinedOutput(cmd)
if _, ok := err.(*exec.ExitError); ok {
return errors.New(string(out))
}
return err
}
func (c *Cli) signExecutable(path string) error {
// Try to parse existing config
conf, err := c.readConfigJSONtoStruct(defaultConfigFilename)
// If an enclave.json exists, check if the path inside it matches the user input and call signWithJSON.
// Otherwise, throw an error.
if err != nil {
if !errors.Is(err, errConfigDoesNotExist) {
return err
}
} else if conf.Exe == path {
return c.signWithJSON(conf)
} else {
return fmt.Errorf("provided path to executable does not match the one in %s", defaultConfigFilename)
}
// If no enclave.json exists, generate a new one.
fmt.Println("Generating new", defaultConfigFilename)
// sane default values
conf = &config.Config{
Exe: path,
Key: defaultPrivKeyFilename,
Debug: true,
HeapSize: 512, //[MB]
ProductID: 1,
SecurityVersion: 1,
}
jsonData, err := json.MarshalIndent(conf, "", " ")
if err != nil {
return err
}
if err := c.fs.WriteFile(defaultConfigFilename, jsonData, 0o644); err != nil {
return err
}
return c.signWithJSON(conf)
}
// Reads the provided File and turns it into a struct
// after some basic sanity check are performed it is returned
// err != nil indicates that the file could not be read or the
// JSON could not be unmarshalled
func (c *Cli) readConfigJSONtoStruct(path string) (*config.Config, error) {
data, err := c.fs.ReadFile(path)
if errors.Is(err, os.ErrNotExist) {
return nil, fmt.Errorf("%w: %v", errConfigDoesNotExist, path)
}
if err != nil {
return nil, err
}
var conf config.Config
if err := json.Unmarshal(data, &conf); err != nil {
return nil, err
}
if err := conf.Validate(); err != nil {
return nil, err
}
// interpret paths relative to the directory containing the config file
dir := filepath.Dir(path)
conf.Exe = filepath.Join(dir, conf.Exe)
conf.Key = filepath.Join(dir, conf.Key)
if err := conf.PopulateContent(c.fs); err != nil {
return nil, fmt.Errorf("failed to populate embedded file content: %w", err)
}
return &conf, nil
}
// Creates a public/secret keypair if the provided secret key does not exist
func (c *Cli) createDefaultKeypair(file string) {
if _, err := c.fs.Stat(file); err != nil {
if !os.IsNotExist(err) {
panic(err)
}
fmt.Println("Generating new " + file)
// SGX requires the RSA exponent to be 3. Go's API does not support this.
if out, err := c.runner.CombinedOutput(exec.Command("openssl", "genrsa", "-out", file, "-3", "3072")); err != nil {
fmt.Println(string(out))
panic(err)
}
pubPath := filepath.Join(filepath.Dir(file), defaultPubKeyFilename)
if out, err := c.runner.CombinedOutput(exec.Command("openssl", "rsa", "-in", file, "-pubout", "-out", pubPath)); err != nil {
fmt.Println(string(out))
panic(err)
}
}
}
// Sign signs an executable built with ego-go.
func (c *Cli) Sign(filename string) error {
if filename == "" {
// When no filename is defined, use an existing enclave.json for reference, or fail otherwise.
conf, err := c.readConfigJSONtoStruct(defaultConfigFilename)
if err != nil {
return err
}
return c.signWithJSON(conf)
}
if filepath.Ext(filename) == ".json" {
// If the supplied filename seems to be a JSON file, interpret it as a config file.
conf, err := c.readConfigJSONtoStruct(filename)
if err != nil {
return err
}
return c.signWithJSON(conf)
}
// If no config exists or no JSON file has been specified, assume the supplied file name is an executable.
return c.signExecutable(filename)
}