github.com/hoffie/larasync@v0.0.0-20151025221940-0384d2bddcef/api/common/sign_test.go (about) 1 package common 2 3 import ( 4 "bytes" 5 "io" 6 "io/ioutil" 7 "net" 8 "net/http" 9 "net/url" 10 "time" 11 12 . "gopkg.in/check.v1" 13 ) 14 15 type SignTests struct { 16 req *http.Request 17 } 18 19 var _ = Suite(&SignTests{}) 20 21 func (t *SignTests) SetUpTest(c *C) { 22 req, err := http.NewRequest("GET", "http://example.org/repositories", nil) 23 c.Assert(err, IsNil) 24 SignWithPassphrase(req, adminSecret) 25 t.req = req 26 } 27 28 func (t *SignTests) adminSigned() bool { 29 return ValidateRequest(t.req, adminPubkey, time.Minute) 30 } 31 32 func (t *SignTests) TestAuthorizationHeader(c *C) { 33 c.Assert(t.req.Header.Get("Authorization"), Not(Equals), "") 34 } 35 36 func (t *SignTests) TestAdminSigningCorrectSignature(c *C) { 37 c.Assert(t.adminSigned(), Equals, true) 38 } 39 40 func (t *SignTests) TestAdminSigningIgnoreUserAgent(c *C) { 41 t.req.Header.Set("User-Agent", "foo") 42 c.Assert(t.adminSigned(), Equals, true) 43 } 44 45 // TestSigningAvoidHeaderMixup verifies that different parts 46 // of the request may not be confused with other parts by the 47 // signature algorithm. 48 func (t *SignTests) TestSigningAvoidHeaderMixup(c *C) { 49 t.req.Header.Set("Header", "value") 50 SignWithPassphrase(t.req, adminSecret) 51 c.Assert(t.adminSigned(), Equals, true) 52 t.req.Header.Del("Header") 53 t.req.Header.Set("Headerval", "ue") 54 c.Assert(t.adminSigned(), Equals, false) 55 } 56 57 func (t *SignTests) TestAdminSigningIgnoreHost(c *C) { 58 // we ignore the Host header as it breaks signing due to 59 // differences in client-side and server-side requests; 60 // the actual host name is still signed as part of the URL 61 t.req.Header.Set("Host", "foo") 62 c.Assert(t.adminSigned(), Equals, true) 63 } 64 65 func (t *SignTests) TestAdminSigningIgnoreAcceptEncoding(c *C) { 66 t.req.Header.Set("Accept-Encoding", "foo") 67 c.Assert(t.adminSigned(), Equals, true) 68 } 69 70 func (t *SignTests) TestAdminSigningNormalizeURL(c *C) { 71 t.req.URL.Host = "" 72 t.req.Host = "example.org" 73 c.Assert(t.adminSigned(), Equals, true) 74 } 75 76 func (t *SignTests) TestAdminSigningEmptyAuthorizationHeader(c *C) { 77 t.req.Header.Set("Authorization", "") 78 c.Assert(t.adminSigned(), Equals, false) 79 } 80 81 func (t *SignTests) TestAdminSigningNonLaraAuthorizationHeader(c *C) { 82 t.req.Header.Set("Authorization", "basic foo") 83 c.Assert(t.adminSigned(), Equals, false) 84 } 85 86 func (t *SignTests) TestAdminSigningShortSig(c *C) { 87 t.req.Header.Set("Authorization", "lara 111") 88 c.Assert(t.adminSigned(), Equals, false) 89 } 90 91 func (t *SignTests) TestAdminSigningMissingAuthorizationSig(c *C) { 92 t.req.Header.Set("Authorization", "lara ") 93 c.Assert(t.adminSigned(), Equals, false) 94 } 95 96 func (t *SignTests) TestAdminSigningBadHexHash(c *C) { 97 t.req.Header.Set("Authorization", "lara 123") 98 c.Assert(t.adminSigned(), Equals, false) 99 } 100 101 func (t *SignTests) TestAdminSigningHashTooShort(c *C) { 102 t.req.Header.Set("Authorization", "lara 1234") 103 c.Assert(t.adminSigned(), Equals, false) 104 } 105 106 func (t *SignTests) TestAdminSigningChangedMethodAuthorizationHeader(c *C) { 107 t.req.Method = "POST" 108 c.Assert(t.adminSigned(), Equals, false) 109 } 110 111 func (t *SignTests) TestAdminSigningAddedHeader(c *C) { 112 t.req.Header.Set("Test", "1") 113 c.Assert(t.adminSigned(), Equals, false) 114 } 115 116 func (t *SignTests) TestAdminSigningChangedUrl(c *C) { 117 newURL, err := url.Parse("http://example.org/repositories?x=3") 118 c.Assert(err, IsNil) 119 t.req.URL = newURL 120 c.Assert(t.adminSigned(), Equals, false) 121 } 122 123 func (t *SignTests) TestAdminSigningOutdatedSignature(c *C) { 124 tenSecsAgo := time.Now().Add(-10 * time.Second) 125 // this will most likely send non-GMT time which is against the HTTP RFC; 126 // as we should handle this as well, it's ok for testing: 127 t.req.Header.Set("Date", tenSecsAgo.Format(time.RFC1123)) 128 SignWithPassphrase(t.req, adminSecret) 129 c.Assert(ValidateRequest(t.req, adminPubkey, 9*time.Second), Equals, false) 130 } 131 132 func (t *SignTests) changeBody() { 133 t.req.Body = ioutil.NopCloser(bytes.NewBuffer([]byte("changed body"))) 134 } 135 136 func (t *SignTests) TestAdminSigningBodyChange(c *C) { 137 t.changeBody() 138 c.Assert(t.adminSigned(), Equals, false) 139 } 140 141 func (t *SignTests) TestAdminSigningBodyTextRead(c *C) { 142 t.changeBody() 143 c.Assert(t.adminSigned(), Equals, false) 144 buf := make([]byte, 100) 145 read, _ := t.req.Body.Read(buf) 146 c.Assert(string(buf[:read]), Equals, "changed body") 147 } 148 149 func (t *SignTests) TestYoungerThanBadHeader(c *C) { 150 t.req.Header.Set("Date", "123") 151 c.Assert(youngerThan(t.req, time.Minute), Equals, false) 152 } 153 154 func (t *SignTests) TestRealRoundTripWithBody(c *C) { 155 t.runRealRoundTrip(c, bytes.NewBufferString("test")) 156 } 157 158 func (t *SignTests) TestRealRoundTripWithEmptyBody(c *C) { 159 t.runRealRoundTrip(c, nil) 160 } 161 162 // TestRealRoundTrip uses the full Go http client/server stack to execute 163 // a real HTTP roundtrip. 164 // This ensures that this process does not mangle the request in a way 165 // which would break signatures. 166 func (t *SignTests) runRealRoundTrip(c *C, body io.Reader) { 167 // passing port :0 to Listen lets it choose a random port 168 listener, err := net.Listen("tcp", "127.0.0.1:0") 169 c.Assert(err, IsNil) 170 defer listener.Close() 171 hostAndPort := listener.Addr().String() 172 173 adminSecret := []byte("test") 174 pubKey, err := GetAdminSecretPubkey(adminSecret) 175 c.Assert(err, IsNil) 176 177 server := &http.Server{ 178 Handler: http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 179 if !ValidateRequest(req, pubKey, 5*time.Second) { 180 rw.WriteHeader(http.StatusUnauthorized) 181 return 182 } 183 rw.Header().Set("X-Lara-Validated", "1") 184 }), 185 } 186 go server.Serve(listener) 187 188 req, err := http.NewRequest("GET", "http://"+hostAndPort+"/foo.txt?x=1", 189 body) 190 c.Assert(err, IsNil) 191 SignWithPassphrase(req, adminSecret) 192 client := &http.Client{} 193 resp, err := client.Do(req) 194 c.Assert(err, IsNil) 195 c.Assert(resp.StatusCode, Equals, 200) 196 c.Assert(resp.Header.Get("X-Lara-Validated"), Equals, "1") 197 }