zig.go (view raw)
1//
2// Copyright (c) 2019 Ted Unangst <tedu@tedunangst.com>
3//
4// Permission to use, copy, modify, and distribute this software for any
5// purpose with or without fee is hereby granted, provided that the above
6// copyright notice and this permission notice appear in all copies.
7//
8// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
16package main
17
18import (
19 "bytes"
20 "crypto"
21 "crypto/rand"
22 "crypto/rsa"
23 "crypto/sha256"
24 "crypto/x509"
25 "encoding/base64"
26 "encoding/pem"
27 "fmt"
28 "io"
29 "net/http"
30 "regexp"
31 "strconv"
32 "strings"
33 "time"
34)
35
36func sb64(data []byte) string {
37 var sb strings.Builder
38 b64 := base64.NewEncoder(base64.StdEncoding, &sb)
39 b64.Write(data)
40 b64.Close()
41 return sb.String()
42
43}
44func b64s(s string) []byte {
45 var buf bytes.Buffer
46 b64 := base64.NewDecoder(base64.StdEncoding, strings.NewReader(s))
47 io.Copy(&buf, b64)
48 return buf.Bytes()
49}
50func sb64sha256(content []byte) string {
51 h := sha256.New()
52 h.Write(content)
53 return sb64(h.Sum(nil))
54}
55
56func zig(keyname string, key *rsa.PrivateKey, req *http.Request, content []byte) {
57 headers := []string{"(request-target)", "date", "host", "content-length", "digest"}
58 var stuff []string
59 for _, h := range headers {
60 var s string
61 switch h {
62 case "(request-target)":
63 s = strings.ToLower(req.Method) + " " + req.URL.RequestURI()
64 case "date":
65 s = req.Header.Get(h)
66 if s == "" {
67 s = time.Now().UTC().Format(http.TimeFormat)
68 req.Header.Set(h, s)
69 }
70 case "host":
71 s = req.Header.Get(h)
72 if s == "" {
73 s = req.URL.Hostname()
74 req.Header.Set(h, s)
75 }
76 case "content-length":
77 s = req.Header.Get(h)
78 if s == "" {
79 s = strconv.Itoa(len(content))
80 req.Header.Set(h, s)
81 req.ContentLength = int64(len(content))
82 }
83 case "digest":
84 s = req.Header.Get(h)
85 if s == "" {
86 s = "SHA-256=" + sb64sha256(content)
87 req.Header.Set(h, s)
88 }
89 }
90 stuff = append(stuff, h+": "+s)
91 }
92
93 h := sha256.New()
94 h.Write([]byte(strings.Join(stuff, "\n")))
95 sig, _ := rsa.SignPKCS1v15(rand.Reader, key, crypto.SHA256, h.Sum(nil))
96 bsig := sb64(sig)
97
98 sighdr := fmt.Sprintf(`keyId="%s",algorithm="%s",headers="%s",signature="%s"`,
99 keyname, "rsa-sha256", strings.Join(headers, " "), bsig)
100 req.Header.Set("Signature", sighdr)
101}
102
103var re_sighdrval = regexp.MustCompile(`(.*)="(.*)"`)
104
105func zag(req *http.Request, content []byte) (string, error) {
106 sighdr := req.Header.Get("Signature")
107
108 var keyname, algo, heads, bsig string
109 for _, v := range strings.Split(sighdr, ",") {
110 m := re_sighdrval.FindStringSubmatch(v)
111 if len(m) != 3 {
112 return "", fmt.Errorf("bad scan: %s from %s\n", v, sighdr)
113 }
114 switch m[1] {
115 case "keyId":
116 keyname = m[2]
117 case "algorithm":
118 algo = m[2]
119 case "headers":
120 heads = m[2]
121 case "signature":
122 bsig = m[2]
123 default:
124 return "", fmt.Errorf("bad sig val: %s", m[1])
125 }
126 }
127 if keyname == "" || algo == "" || heads == "" || bsig == "" {
128 return "", fmt.Errorf("missing a sig value")
129 }
130
131 key := zaggy(keyname)
132 if key == nil {
133 return "", fmt.Errorf("no key for %s", keyname)
134 }
135 headers := strings.Split(heads, " ")
136 var stuff []string
137 for _, h := range headers {
138 var s string
139 switch h {
140 case "(request-target)":
141 s = strings.ToLower(req.Method) + " " + req.URL.RequestURI()
142 case "host":
143 s = req.Host
144 default:
145 s = req.Header.Get(h)
146 }
147 stuff = append(stuff, h+": "+s)
148 }
149
150 h := sha256.New()
151 h.Write([]byte(strings.Join(stuff, "\n")))
152 sig := b64s(bsig)
153 err := rsa.VerifyPKCS1v15(key, crypto.SHA256, h.Sum(nil), sig)
154 if err != nil {
155 return "", err
156 }
157 return keyname, nil
158}
159
160func pez(s string) (pri *rsa.PrivateKey, pub *rsa.PublicKey, err error) {
161 block, _ := pem.Decode([]byte(s))
162 if block == nil {
163 err = fmt.Errorf("no pem data")
164 return
165 }
166 switch block.Type {
167 case "PUBLIC KEY":
168 var k interface{}
169 k, err = x509.ParsePKIXPublicKey(block.Bytes)
170 if k != nil {
171 pub, _ = k.(*rsa.PublicKey)
172 }
173 case "RSA PUBLIC KEY":
174 pub, err = x509.ParsePKCS1PublicKey(block.Bytes)
175 case "RSA PRIVATE KEY":
176 pri, err = x509.ParsePKCS1PrivateKey(block.Bytes)
177 if err == nil {
178 pub = &pri.PublicKey
179 }
180 default:
181 err = fmt.Errorf("unknown key type")
182 }
183 return
184}
185
186func zem(i interface{}) (string, error) {
187 var b pem.Block
188 var err error
189 switch k := i.(type) {
190 case *rsa.PrivateKey:
191 b.Type = "RSA PRIVATE KEY"
192 b.Bytes = x509.MarshalPKCS1PrivateKey(k)
193 case *rsa.PublicKey:
194 b.Type = "PUBLIC KEY"
195 b.Bytes, err = x509.MarshalPKIXPublicKey(k)
196 default:
197 err = fmt.Errorf("unknown key type: %s", k)
198 }
199 if err != nil {
200 return "", err
201 }
202 return string(pem.EncodeToMemory(&b)), nil
203}