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 "strings"
32 "time"
33)
34
35func sb64(data []byte) string {
36 var sb strings.Builder
37 b64 := base64.NewEncoder(base64.StdEncoding, &sb)
38 b64.Write(data)
39 b64.Close()
40 return sb.String()
41
42}
43func b64s(s string) []byte {
44 var buf bytes.Buffer
45 b64 := base64.NewDecoder(base64.StdEncoding, strings.NewReader(s))
46 io.Copy(&buf, b64)
47 return buf.Bytes()
48}
49func sb64sha256(content []byte) string {
50 h := sha256.New()
51 h.Write(content)
52 return sb64(h.Sum(nil))
53}
54
55func zig(keyname string, key *rsa.PrivateKey, req *http.Request, content []byte) {
56 headers := []string{"(request-target)", "date", "host", "content-type", "digest"}
57 var stuff []string
58 for _, h := range headers {
59 var s string
60 switch h {
61 case "(request-target)":
62 s = strings.ToLower(req.Method) + " " + req.URL.RequestURI()
63 case "date":
64 s = req.Header.Get(h)
65 if s == "" {
66 s = time.Now().UTC().Format(http.TimeFormat)
67 req.Header.Set(h, s)
68 }
69 case "host":
70 s = req.Header.Get(h)
71 if s == "" {
72 s = req.URL.Hostname()
73 req.Header.Set(h, s)
74 }
75 case "content-type":
76 s = req.Header.Get(h)
77 case "digest":
78 s = req.Header.Get(h)
79 if s == "" {
80 s = "SHA-256=" + sb64sha256(content)
81 req.Header.Set(h, s)
82 }
83 }
84 stuff = append(stuff, h+": "+s)
85 }
86
87 h := sha256.New()
88 h.Write([]byte(strings.Join(stuff, "\n")))
89 sig, _ := rsa.SignPKCS1v15(rand.Reader, key, crypto.SHA256, h.Sum(nil))
90 bsig := sb64(sig)
91
92 sighdr := fmt.Sprintf(`keyId="%s",algorithm="%s",headers="%s",signature="%s"`,
93 keyname, "rsa-sha256", strings.Join(headers, " "), bsig)
94 req.Header.Set("Signature", sighdr)
95}
96
97var re_sighdrval = regexp.MustCompile(`(.*)="(.*)"`)
98
99func zag(req *http.Request, content []byte) (string, error) {
100 sighdr := req.Header.Get("Signature")
101
102 var keyname, algo, heads, bsig string
103 for _, v := range strings.Split(sighdr, ",") {
104 m := re_sighdrval.FindStringSubmatch(v)
105 if len(m) != 3 {
106 return "", fmt.Errorf("bad scan: %s from %s\n", v, sighdr)
107 }
108 switch m[1] {
109 case "keyId":
110 keyname = m[2]
111 case "algorithm":
112 algo = m[2]
113 case "headers":
114 heads = m[2]
115 case "signature":
116 bsig = m[2]
117 default:
118 return "", fmt.Errorf("bad sig val: %s", m[1])
119 }
120 }
121 if keyname == "" || algo == "" || heads == "" || bsig == "" {
122 return "", fmt.Errorf("missing a sig value")
123 }
124
125 key := zaggy(keyname)
126 if key == nil {
127 return "", fmt.Errorf("no key for %s", keyname)
128 }
129 headers := strings.Split(heads, " ")
130 var stuff []string
131 for _, h := range headers {
132 var s string
133 switch h {
134 case "(request-target)":
135 s = strings.ToLower(req.Method) + " " + req.URL.RequestURI()
136 case "host":
137 s = req.Host
138 default:
139 s = req.Header.Get(h)
140 }
141 stuff = append(stuff, h+": "+s)
142 }
143
144 h := sha256.New()
145 h.Write([]byte(strings.Join(stuff, "\n")))
146 sig := b64s(bsig)
147 err := rsa.VerifyPKCS1v15(key, crypto.SHA256, h.Sum(nil), sig)
148 if err != nil {
149 return "", err
150 }
151 return keyname, nil
152}
153
154func pez(s string) (pri *rsa.PrivateKey, pub *rsa.PublicKey, err error) {
155 block, _ := pem.Decode([]byte(s))
156 if block == nil {
157 err = fmt.Errorf("no pem data")
158 return
159 }
160 switch block.Type {
161 case "PUBLIC KEY":
162 var k interface{}
163 k, err = x509.ParsePKIXPublicKey(block.Bytes)
164 if k != nil {
165 pub, _ = k.(*rsa.PublicKey)
166 }
167 case "RSA PUBLIC KEY":
168 pub, err = x509.ParsePKCS1PublicKey(block.Bytes)
169 case "RSA PRIVATE KEY":
170 pri, err = x509.ParsePKCS1PrivateKey(block.Bytes)
171 if err == nil {
172 pub = &pri.PublicKey
173 }
174 default:
175 err = fmt.Errorf("unknown key type")
176 }
177 return
178}
179
180func zem(i interface{}) (string, error) {
181 var b pem.Block
182 var err error
183 switch k := i.(type) {
184 case *rsa.PrivateKey:
185 b.Type = "RSA PRIVATE KEY"
186 b.Bytes = x509.MarshalPKCS1PrivateKey(k)
187 case *rsa.PublicKey:
188 b.Type = "PUBLIC KEY"
189 b.Bytes, err = x509.MarshalPKIXPublicKey(k)
190 default:
191 err = fmt.Errorf("unknown key type: %s", k)
192 }
193 if err != nil {
194 return "", err
195 }
196 return string(pem.EncodeToMemory(&b)), nil
197}