forked from gwitmond/ecca-proxy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
crypto.go
248 lines (205 loc) · 8.15 KB
/
crypto.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
// Ecca Authentication Proxy
//
// Handles Eccentric Authentication in a web proxy for browsers.
//
// Copyright 2013, Guido Witmond <guido@witmond.nl>
// Licensed under AGPL v3 or later. See LICENSE
package main // eccaproxy
import (
"log"
"fmt"
"net/url"
"crypto/x509"
"io"
"io/ioutil"
"encoding/pem"
"errors"
"bytes"
"os"
"os/exec"
"strings"
"github.com/gwitmond/eccentric-authentication" // package eccentric
)
// fetchCertificate GETs the url and parses the page as a PEM encoded certificate.
func fetchCertificatePEM(certificateURL string) ([]byte, error) {
certURL, err := url.Parse(certificateURL)
if err != nil { return nil, err }
// encode query-parameters properly.
q := certURL.Query()
certURL.RawQuery = q.Encode()
log.Printf("certificateURL is: %v, RawQuery is %#v, RequestURI is %v\n", certificateURL, certURL.Query(), certURL.RequestURI())
certHostname := getHostname(certURL.Host)
client, err := makeClient(certHostname)
if err != nil { return nil, err }
log.Printf("Fetching public key for %v\n", certificateURL)
resp, err := client.Get(certificateURL)
if err != nil { return nil, err }// check(err)
body, err := ioutil.ReadAll(resp.Body)
if err != nil { return nil, err }// check(err)
//log.Printf("Received: %q\n", body)
// decode pem...,
pemBlock, _ := pem.Decode(body)
// check type...,
if pemBlock.Type != "CERTIFICATE" {
return nil, errors.New("Did not receive a PEM encoded certificate")
}
// parse der to validate the data...,
cert, err := x509.ParseCertificate(pemBlock.Bytes)
if err != nil { return nil, err } //check(err)
log.Printf("Fetched cert for %v", cert.Subject.CommonName)
// but return the PEM so we can copy it to disk for /usr/bin/openssl
return body, nil
}
// Sign a message
// Sign a message to be posted in public places.
// This attaches ownership of the private key to the message.
func Sign(privkeyPEM []byte, certPEM []byte, message string) (string, error) {
// log.Printf("signing %v\n", message)
if len(message) == 0 {
return "", errors.New("Cannot sign empty message")
}
keyFileName := makeTempfile("ecca-key-", privkeyPEM)
defer os.Remove(keyFileName)
certFileName := makeTempfile("ecca-cert-", certPEM)
defer os.Remove(certFileName)
err, stdout, stderr := run(strings.NewReader(message),
"openssl", "smime", "-sign", "-signer", certFileName, "-inkey", keyFileName)
if err != nil {
return "", errors.New(fmt.Sprintf("Error signing message. Openssl says: %s\n", stderr.String()))
}
signature := stdout.String()
return signature, nil
}
// Verify the message
// Return a boolean whether the message is signed by the signature.
// Return the message to show on screen, can't trust the server.
func Verify(message string, signature string, caChain []x509.Certificate) (bool, string) {
caFile, err := ioutil.TempFile("", "ecca-ca-")
check(err)
caFilename := caFile.Name()
defer os.Remove(caFilename)
for _, cert := range caChain {
pem.Encode(caFile, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
}
caFile.Close()
// TODO: create template to merge message and signature in a valid openssl smime like format
err, stdout, stderr := run(strings.NewReader(signature),
"openssl", "smime", "-verify", "-CAfile", caFilename)
if err != nil {
log.Printf("Error verifying message. Openssl says: %s\n", stderr.String())
return false, stderr.String() // return error message for now.
}
// Note: with openssl smime signing, the true message is in the signature, we return what we get back from openssl
// TODO: return message == stdout.String(), plus "error message in case it is false"
return true, stdout.String() // or Bytes()
}
func run(stdin io.Reader, command string, args ... string) (error, bytes.Buffer, bytes.Buffer) {
runner := exec.Command(command, args...)
runner.Stdin = stdin
var stdout bytes.Buffer
var stderr bytes.Buffer
runner.Stdout = &stdout
runner.Stderr = &stderr
err := runner.Run()
if err != nil {
log.Printf("Error with running command: \"%v %v\"\nerror is: %v\nstderr is: %v\n", command, args, err, stderr.String())
}
return err, stdout, stderr
}
// Fetch the identity (users' client certificate) from the signed message.
func FetchIdentity(signature string) *x509.Certificate {
err, pk7, stderr := run(strings.NewReader(signature),
"openssl", "smime", "-pk7out")
if err != nil {
log.Fatal(stderr.String())
// this dies here.
}
// pipe the pk7 data to extract the user certificate
err, certPEM, stderr := run(bytes.NewReader(pk7.Bytes()),
"openssl", "pkcs7", "-print_certs")
if err != nil {
log.Fatal(stderr.String())
// this dies here too.
}
cert := eccentric.PEMDecode(certPEM.Bytes())
return &cert
}
// make a tempfile with given data.
// return the filename, caller needs to defer.os.Remove it.
func makeTempfile(prefix string, data []byte) string {
tempFile, err := ioutil.TempFile("", prefix)
check(err) // die on error
tempFileName := tempFile.Name()
tempFile.Write(data)
tempFile.Close()
return tempFileName
}
// Sign and Encrypt a message.
// Sign first and encrypt the signed message as not to leak who it's from to observers en route to the recipient.
// (i.e. only the recipient get to know and verify the sender)
func SignAndEncryptPEM(signPrivkeyPEM, signCertPEM, recipientCertPEM []byte, message string) []byte {
if len(message) == 0 {
log.Fatal(errors.New("Cannot sign empty message"))
}
recipCertFileName := makeTempfile("ecca-recipcert-", recipientCertPEM)
defer os.Remove(recipCertFileName)
signKeyFileName := makeTempfile("ecca-signkey-", signPrivkeyPEM)
defer os.Remove(signKeyFileName)
signCertFileName := makeTempfile("ecca-signcert-", signCertPEM)
defer os.Remove(signCertFileName)
err, signStdout, signStderr := run(strings.NewReader(message),
"openssl", "smime", "-sign", "-signer", signCertFileName, "-inkey", signKeyFileName)
if err != nil {
log.Fatal(errors.New(fmt.Sprintf("Error signing message. Error: %v\nOpenssl says: %s\n", err, signStderr.String())))
}
// pipe the output of signing into the encryption
signature := signStdout.Bytes()
err, encrStdout, encrStderr := run(bytes.NewReader(signature),
"openssl", "smime", "-encrypt", "-aes128", "-binary", "-outform", "DER", recipCertFileName)
if err != nil {
log.Fatal(errors.New(fmt.Sprintf("Error encrypting message. Error: %v\nOpenssl says: %s\n", err, encrStderr.String())))
log.Fatal(err)
}
cipherDER := encrStdout.Bytes()
cipherPEM := pem.EncodeToMemory(&pem.Block{Type: "ECCA ENCRYPTED SIGNED MESSAGE", Bytes: cipherDER})
return cipherPEM
}
// Decrypt with own key and verify signature to retrieve sender's identity
func DecryptAndVerify(cipherPEM []byte, privkeyPEM []byte) (string, *x509.Certificate, error) {
if len(cipherPEM) == 0 {
return "", nil, errors.New( "Error, no secret message here. In fact, nothing at all.")
}
cipherBlock, _ := pem.Decode(cipherPEM)
if cipherBlock == nil {
return "", nil, errors.New("Error, no secret message here. Nothing we could recognize.")
}
if cipherBlock.Type != "ECCA ENCRYPTED SIGNED MESSAGE" {
return "", nil, errors.New("Error, expecting -----ECCA ENCRYPTED SIGNED MESSAGE-----")
}
keyFileName := makeTempfile("ecca-key-", privkeyPEM)
defer os.Remove(keyFileName)
err, stdout, stderr := run(bytes.NewReader(cipherBlock.Bytes),
"openssl", "smime", "-decrypt", "-binary", "-inform", "DER", "-inkey", keyFileName)
if err != nil {
return "", nil, errors.New(fmt.Sprintf("Error decrypting message. Openssl says: %s\n", stderr.String()))
}
signedMessage := stdout.String()
// Verify message against senderCert against FPCA of sender.
senderCert := FetchIdentity(signedMessage)
sender := senderCert.Subject.CommonName
username, hostname, err := eccentric.ParseCN(sender)
log.Printf("Identity from message is %s, username is %s, hostname is %s", sender, username, hostname)
check(err)
// Get the root CA of the sender
rootCACert, err := eccentric.FetchRootCA(hostname)
check(err)
// Fetch the chain (and validate that our idCert is a valid Eccentric cert)
chain, err := eccentric.ValidateEccentricCertificateChain(senderCert, rootCACert)
check(err)
valid, message := Verify("ignore", signedMessage, chain)
if valid {
return message, senderCert, nil
} else {
return "(invalid signature, suppressing message)", nil, nil
}
}