-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
360 lines (306 loc) · 9.82 KB
/
main.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
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
package main
import (
"bytes"
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/json"
"encoding/pem"
"fmt"
"github.com/joho/godotenv"
"io/ioutil"
"net/http"
"os"
"strings"
)
// URLs and constants(signing agent) for the application
const (
loginURL = "http://localhost:8000/api/users/login"
logsURL = "http://localhost:8000/api/logs"
signatureAgent = "SigNature"
)
// Payload represents the JSON payload structure of Credentials to be uploaded to the Database.
type Payload struct {
FileName string `json:"name"`
Hash string `json:"hash"`
SignedReference string `json:"signedReference"`
KeyName string `json:"keyName"`
SignAgent string `json:"signAgent"`
File []byte `json:"file"`
KeyFile []byte `json:"keyFile"`
}
// SignFile signs the content of a file using RSA private key, further it creates a hash of the file as well as
// returns the signature reference of the file.
func SignFile(filePath string, privateKey *rsa.PrivateKey) ([]byte, error) {
content, err := ioutil.ReadFile(filePath)
if err != nil {
return nil, err
}
hash := sha256.Sum256(content)
signature, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hash[:])
if err != nil {
return nil, err
}
return signature, nil
}
// UploadPayload function uploads the payload to the server
func UploadPayload(payload *Payload, token string) error {
// Encode payload to JSON
jsonData, err := json.Marshal(payload)
if err != nil {
return err
}
// Create a new HTTP client
client := &http.Client{}
// Create a new request with the payload data
req, err := http.NewRequest("POST", logsURL, bytes.NewBuffer(jsonData))
if err != nil {
return err
}
// Set the content type header
req.Header.Set("Content-Type", "application/json")
// Set the Authorization header with the token
req.Header.Set("Authorization", "Bearer "+token)
// Send the request to the server
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
// Check the response status code
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("upload failed: %s", resp.Status)
}
return nil
}
// parsePrivateKey parses RSA private key from bytes
func parsePrivateKey(privateKeyBytes []byte) (*rsa.PrivateKey, error) {
block, _ := pem.Decode(privateKeyBytes)
if block == nil {
return nil, fmt.Errorf("failed to parse PEM block containing the private key")
}
privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse private key: %w", err)
}
rsaPrivateKey, ok := privateKey.(*rsa.PrivateKey)
if !ok {
return nil, fmt.Errorf("private key is not an RSA private key")
}
return rsaPrivateKey, nil
}
// Login function performs user login and returns the token
func Login(email, password string) (string, error) {
// Prepare login payload with email and password in JSON format
loginPayload := map[string]string{"email": email, "password": password}
loginData, err := json.Marshal(loginPayload)
if err != nil {
return "", err
}
// Send login request to the server
resp, err := http.Post(loginURL, "application/json", bytes.NewBuffer(loginData))
if err != nil {
return "", err
}
defer resp.Body.Close()
// Check response status code after login request
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("login failed: %s", resp.Status)
}
// Decode login response and extract the token from it
var loginResponse map[string]string
if err := json.NewDecoder(resp.Body).Decode(&loginResponse); err != nil {
return "", err
}
//set the token
token, ok := loginResponse["accessToken"]
if !ok {
return "", fmt.Errorf("accessToken not found in login response")
}
return token, nil
}
// VerifyFile verifies the signature of the file using the rsa public key
func VerifyFile(filePath string, publicKey *rsa.PublicKey) error {
// Read the file to be verified
content, err := ioutil.ReadFile(filePath)
if err != nil {
return fmt.Errorf("error reading file: %w", err)
}
// Extract the hash and signature from the file's metadata
var hash, signature []byte
lines := strings.Split(string(content), "\n")
for _, line := range lines {
if strings.HasPrefix(line, "Hash:") {
hash, err = hex.DecodeString(strings.TrimSpace(line[len("Hash:"):]))
if err != nil {
return fmt.Errorf("error decoding hash: %w", err)
}
}
if strings.HasPrefix(line, "SignedReference:") {
signature, err = base64.StdEncoding.DecodeString(strings.TrimSpace(line[len("SignedReference:"):]))
if err != nil {
return fmt.Errorf("error decoding signature: %w", err)
}
}
}
// Verify the signature against the hash using the rsa public key
err = rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, hash, signature)
if err != nil {
return fmt.Errorf("signature verification failed: %w", err)
}
return nil
}
// parsePublicKey parses RSA public key from bytes
func parsePublicKey(publicKeyBytes []byte) (*rsa.PublicKey, error) {
block, _ := pem.Decode(publicKeyBytes)
if block == nil {
return nil, fmt.Errorf("failed to parse PEM block containing the public key")
}
// Parse public key using ParsePKIXPublicKey
publicKeyInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse public key: %w", err)
}
// Type assert the parsed public key to *rsa.PublicKey
publicKey, ok := publicKeyInterface.(*rsa.PublicKey)
if !ok {
return nil, fmt.Errorf("failed to convert public key to RSA public key")
}
return publicKey, nil
}
// LoadPublicKey loads RSA public key from file
func LoadPublicKey(publicKeyFile string) (*rsa.PublicKey, error) {
// Read public key file into bytes
publicKeyBytes, err := ioutil.ReadFile(publicKeyFile)
if err != nil {
return nil, fmt.Errorf("error reading public key file: %w", err)
}
// Parse public key from bytes
publicKey, err := parsePublicKey(publicKeyBytes)
if err != nil {
return nil, fmt.Errorf("error parsing public key: %w", err)
}
return publicKey, nil
}
func main() {
// Load environment variables from .env file
if err := godotenv.Load(); err != nil {
fmt.Println("Error loading .env file:", err)
os.Exit(1)
}
if len(os.Args) < 3 {
fmt.Println("Usage: ./SigNature <commands>")
fmt.Println("Commands:")
fmt.Println(" sign -priv <private_key_file> -pub <public_key_file> -f <file>")
fmt.Println(" verify -pub <public_key_file> -f <file>")
os.Exit(1)
}
command := os.Args[1]
switch command {
case "sign":
if len(os.Args) < 8 {
fmt.Println("Usage: ./SigNature sign -priv <private_key_file> -pub <public_key_file> -f <file>")
os.Exit(1)
}
privateKeyFile := os.Args[3]
publicKeyfile := os.Args[5]
filePath := os.Args[7]
// Read private key file set to bytes variable
privateKeyBytes, err := ioutil.ReadFile(privateKeyFile)
if err != nil {
fmt.Println("Error reading private key file:", err)
os.Exit(1)
}
publicKeyBytes, err := ioutil.ReadFile(publicKeyfile)
if err != nil {
fmt.Println("Error reading private key file:", err)
os.Exit(1)
}
// Parse private key from bytes variable
privateKey, err := parsePrivateKey(privateKeyBytes)
if err != nil {
fmt.Println("Error parsing private key:", err)
os.Exit(1)
}
// Sign file using the user provided private key
signature, err := SignFile(filePath, privateKey)
if err != nil {
fmt.Println("Error signing file:", err)
os.Exit(1)
}
// Encode signature to base64
signedReference := base64.StdEncoding.EncodeToString(signature)
// Generate a sha256 hash of the file and create a hex string
content, err := ioutil.ReadFile(filePath)
if err != nil {
fmt.Println("Error reading file:", err)
os.Exit(1)
}
hash := sha256.Sum256(content)
hashString := hex.EncodeToString(hash[:])
fmt.Println("Hash of the file:", hashString)
// Write the signature reference and hash to the file's metadata
metadata := []byte(fmt.Sprintf("SignedReference: %s\nHash: %s\n", signedReference, hashString))
contentWithMetadata := append(metadata, content...)
// Write the file with metadata
err = ioutil.WriteFile(filePath, contentWithMetadata, 0644)
if err != nil {
fmt.Println("Error writing file with metadata:", err)
os.Exit(1)
}
fmt.Println("File signed successfully!")
// Create the payload with the file name, hash, signature reference, public key name, signature agent, file content and public key file
payload := &Payload{
FileName: filePath,
Hash: hashString,
SignedReference: signedReference,
KeyName: publicKeyfile,
SignAgent: signatureAgent,
File: content,
KeyFile: publicKeyBytes,
}
// Get email and password from environment variables
email := os.Getenv("EMAIL")
password := os.Getenv("PASSWORD")
// Login using the email and password and retrieve token
token, err := Login(email, password)
if err != nil {
fmt.Println("Error logging in:", err)
os.Exit(1)
}
// Upload payload to server with the token in Authorization header
err = UploadPayload(payload, token)
if err != nil {
fmt.Println("Error uploading payload:", err)
os.Exit(1)
}
fmt.Println("Payload uploaded successfully!")
case "verify":
if len(os.Args) < 6 {
fmt.Println("Usage: ./SigNature verify -pub <public_key_file> -f <file>")
os.Exit(1)
}
publicKeyFile := os.Args[3]
filePath := os.Args[5]
// Load public key from file
publicKey, err := LoadPublicKey(publicKeyFile)
if err != nil {
fmt.Println("Error loading public key:", err)
os.Exit(1)
}
// Verify the signature of the file using the rsa public key
err = VerifyFile(filePath, publicKey)
if err != nil {
fmt.Println("Error verifying file:", err)
os.Exit(1)
}
fmt.Println("File verified successfully!")
default:
fmt.Println("Invalid command. Available commands: sign, verify")
os.Exit(1)
}
}