2019-07-08 15:11:06 +02:00
package http
import (
2019-07-09 14:50:31 +02:00
"crypto/x509"
"encoding/pem"
2019-07-16 10:29:24 +02:00
"errors"
2019-07-09 14:50:31 +02:00
"fmt"
2019-07-08 15:11:06 +02:00
"net/http"
"net/http/httptest"
2019-07-09 14:50:31 +02:00
"strings"
2019-07-08 15:11:06 +02:00
"testing"
2019-07-09 14:50:31 +02:00
"time"
"github.com/lestrrat-go/jwx/jwa"
"github.com/lestrrat-go/jwx/jwt"
2019-07-08 15:11:06 +02:00
)
2019-07-09 14:50:31 +02:00
const publicKey = `
-- -- - BEGIN PUBLIC KEY -- -- -
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2yUfHH6SRYKvBTemrefi
Hk4L4qkcc4skl4QCaHOkfgA4VcGKG2nXysYuZK7AzNOcHQVi + e4BwN + BfIZtwEU5
7 Ogctb5eg8ksxxLjS7eSRfQIvPGfAbJ12R9OoOWcue / CdUy / YMec4R / o4 + tZ45S6
QQWIMhLqYljw + s1Runda3K8Q8lOdJ4yEZckXaZr1waNJikC7oGpT7ClAgdbvWIbo
N18G1OluRn + 3 WNdcN6V + vIj8c9dGs92bgTPX4cn3RmB / 80 BDfzeFiPMRw5xaq66F
42 zXzllkTqukQPk2wmO5m9pFy0ciRve + awfgbTtZRZOEpTSWLbbpOfd4RQ5YqDWJ
mQIDAQAB
-- -- - END PUBLIC KEY -- -- -
`
const privateKey = `
-- -- - BEGIN PRIVATE KEY -- -- -
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDbJR8cfpJFgq8F
N6at5 + IeTgviqRxziySXhAJoc6R + ADhVwYobadfKxi5krsDM05wdBWL57gHA34F8
hm3ARTns6By1vl6DySzHEuNLt5JF9Ai88Z8BsnXZH06g5Zy578J1TL9gx5zhH + jj
61 njlLpBBYgyEupiWPD6zVG6d1rcrxDyU50njIRlyRdpmvXBo0mKQLugalPsKUCB
1 u9Yhug3XwbU6W5Gf7dY11w3pX68iPxz10az3ZuBM9fhyfdGYH / zQEN / N4WI8xHD
nFqrroXjbNfOWWROq6RA + TbCY7mb2kXLRyJG975rB + BtO1lFk4SlNJYttuk593hF
DlioNYmZAgMBAAECggEADvr6pXgBh77nN / QV8M1pJ6kuJtBooX1hgvoDMCC3neVl
9 HbGehlCJxplEXzgsR / GDDXSDkO22vhsYZbO6dXRn + A + Fi5tR5T4 + qLP5t0loqKL
9 l6OAA + y / qSlO1p23D8Hi / 0 zF + qNTtZflTUBcA06rjcymDmyzAZIctyWOajvDSbK
Df0ZvKYPnwG5gjF01hPS2VJicv / O0HXLN7elq / jio1dwvLa2JjPyXhWBkHqnJBcq
ncWP9IEJQmhQ8ijNEg78uLtiNZQ4 + GcXNBlwhM7JER6X / AxSxEZ / 7 fjZog685yUH
3i F820SnStOJQQci / RMMPOsK6cM7BiJxGp2W12EOAQKBgQD85UdCDro6zpblpAw7
Gw82SkWGksJXuGlTX + nj3 / 3i IiEb4ATCvZufYXALGNtiG0tPHDMBQCKLYrbLE1pt
9 uIU / IbDFPeQk8rR / b7IHu0gv3463p6r7WVhzY2 / JCororKYQk4zbuk3cNYtlV76
ojnNY1EFDLK / 1 nGT6QDxDA7Z5wKBgQDd1chB2qlbljRzYFwKrWXZ0COtbnEGPnUz
rLvSlAvYlZSKuB / vXkHGepxdlAjDGgX6xkKSl1TKb8UWQ9JSv0MPGBcMPukuwCAL
BOobyvd1mln6f / C7FrATkRbrG + r8RAQTwR + eknwYYOPAS / PpXm8gZvVntiahihFd
NqQtud8QfwKBgQDGV + xzWqmkxbKDmQ4erTJZGhc9XI0fz3qL8YW3O04btTjSa / hP
4 / XSItGFYpFteIqwGSXHrU1qlJlY3GzoIeFfJE9tYVxpAADqgWDIA7lnHcka0s8P
eLky48xwRSTt5ES + NgKvRCWVXeIdDjHX0LQU6ff5ReRLoRyjLPOYGiTrsQKBgAmq
z1dPWCINoauFf31XoSCk2Wktbu9 + uUzPMkAzA3Ek05xX + cxMp0EnBrltQhR + hdQv
36 bTwXYw + L3HptrESv / VZOu7sh2 / caYJSMp9RdtyJomsGamNi47Ou9jzFoJ31FWo
DOC0MYQ + dK5koPSCkQUwd3FVlsljYu5U + 0 Ki3v2xAoGASIMhNHOvz + Ay2otovVFN
gfRGTnepw8znHbkr10IG97BWd4VbFnHRdpYbtk8fH0UOyUVMrcY0B2 / d73Rzqze3
iZ //FXIDTtmKnVS/ZhC2w0AH8Piziy3NW3G6jRZN6+9NpOf/BIc4pfzgUJ3RqHz/
IeONX + 52 k6gz1SCjPgSUlTs =
-- -- - END PRIVATE KEY -- -- -
`
2019-07-08 15:11:06 +02:00
func TestJWTProtect ( t * testing . T ) {
tests := [ ] struct {
name string
cookie * http . Cookie
2019-07-16 10:29:24 +02:00
headers * http . Header
2019-07-09 14:50:31 +02:00
server * httptest . Server
2019-07-16 10:29:24 +02:00
authorizer func ( r * http . Request , claims map [ string ] interface { } ) error
2019-07-08 15:11:06 +02:00
expectedStatusCode int
} {
{
"no cookie" ,
nil ,
2019-07-09 14:50:31 +02:00
nil ,
2019-07-16 10:29:24 +02:00
nil ,
func ( r * http . Request , claims map [ string ] interface { } ) error { return nil } ,
2019-07-08 15:11:06 +02:00
http . StatusForbidden ,
} ,
{
2019-07-09 14:50:31 +02:00
"bad server" ,
& http . Cookie {
Name : "auth" ,
Value : "irrelevantgibberish" ,
} ,
nil ,
2019-07-16 10:29:24 +02:00
nil ,
func ( r * http . Request , claims map [ string ] interface { } ) error { return nil } ,
2019-07-09 14:50:31 +02:00
http . StatusInternalServerError ,
} ,
{
"non-json response" ,
2019-07-08 15:11:06 +02:00
& http . Cookie {
Name : "auth" ,
Value : "irrelevantgibberish" ,
} ,
2019-07-16 10:29:24 +02:00
nil ,
2019-07-09 14:50:31 +02:00
httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
w . Write ( [ ] byte ( "here's some bytes 4 y'all" ) )
} ) ) ,
2019-07-16 10:29:24 +02:00
func ( r * http . Request , claims map [ string ] interface { } ) error { return nil } ,
2019-07-08 15:11:06 +02:00
http . StatusInternalServerError ,
} ,
2019-07-09 14:50:31 +02:00
{
"bad key value" ,
& http . Cookie {
Name : "auth" ,
Value : "irrelevantgibberish" ,
} ,
2019-07-16 10:29:24 +02:00
nil ,
2019-07-09 14:50:31 +02:00
httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2019-07-19 14:49:35 +02:00
w . Write ( [ ] byte ( ` { "keys":["not really a key","me neither"]} ` ) )
2019-07-09 14:50:31 +02:00
} ) ) ,
2019-07-16 10:29:24 +02:00
func ( r * http . Request , claims map [ string ] interface { } ) error { return nil } ,
2019-07-19 14:49:35 +02:00
http . StatusForbidden ,
2019-07-09 14:50:31 +02:00
} ,
{
"invalid key" ,
& http . Cookie {
Name : "auth" ,
Value : "irrelevantgibberish" ,
} ,
2019-07-16 10:29:24 +02:00
nil ,
2019-07-09 14:50:31 +02:00
httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2019-07-19 14:49:35 +02:00
w . Write ( [ ] byte ( ` { "keys":["-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCATZAMIIBCgKCAQEA2yUfHH6SRYKvBTemrefi\nHk4L4qkcc4skl4QCaHOkfgA4VcGKG2nXysYuZK7AzNOcHQVi+e4BwN+BfIZtwEU5\n7Ogctb5eg8ksxxLjS7eSRfQIvPGfAbJ12R9OoOWcue/CdUy/YMec4R/o4+tZ45S6\nQQWIMhLqYljw+s1Runda3K8Q8lOdJ4yEZckXaZr1waNJikC7oGpT7ClAgdbvWIbo\nN18G1OluRn+3WNdcN6V+vIj8c9dGs92bgTPX4cn3RmB/80BDfzeFiPMRw5xaq66F\n42zXzllkTqukQPk2wmO5m9pFy0ciRve+awfgbTtZRZOEpTSWLbbpOfd4RQ5YqDWJ\nmQIDAQAB\n-----END PUBLIC KEY-----"]} ` ) )
2019-07-09 14:50:31 +02:00
} ) ) ,
2019-07-16 10:29:24 +02:00
func ( r * http . Request , claims map [ string ] interface { } ) error { return nil } ,
2019-07-19 14:49:35 +02:00
http . StatusForbidden ,
2019-07-09 14:50:31 +02:00
} ,
{
"valid key, bad auth token" ,
& http . Cookie {
Name : "auth" ,
Value : "irrelevantgibberish" ,
} ,
2019-07-16 10:29:24 +02:00
nil ,
2019-07-09 14:50:31 +02:00
httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
w . Write ( [ ] byte (
2019-07-19 14:49:35 +02:00
fmt . Sprintf ( ` { "keys":["%s"]} ` , strings . ReplaceAll ( publicKey , "\n" , ` \n ` ) ) ,
2019-07-09 14:50:31 +02:00
) )
} ) ) ,
2019-07-16 10:29:24 +02:00
func ( r * http . Request , claims map [ string ] interface { } ) error { return nil } ,
2019-07-09 14:50:31 +02:00
http . StatusForbidden ,
} ,
{
"valid key, valid token" ,
& http . Cookie {
Name : "auth" ,
Value : ( func ( ) string {
token := jwt . New ( )
token . Set ( "exp" , time . Now ( ) . Add ( time . Hour ) )
keyBytes , _ := pem . Decode ( [ ] byte ( privateKey ) )
privKey , _ := x509 . ParsePKCS8PrivateKey ( keyBytes . Bytes )
b , _ := token . Sign ( jwa . RS256 , privKey )
return string ( b )
} ) ( ) ,
} ,
2019-07-16 10:29:24 +02:00
nil ,
httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
w . Write ( [ ] byte (
2019-07-19 14:49:35 +02:00
fmt . Sprintf ( ` { "keys":["%s"]} ` , strings . ReplaceAll ( publicKey , "\n" , ` \n ` ) ) ,
2019-07-16 10:29:24 +02:00
) )
} ) ) ,
func ( r * http . Request , claims map [ string ] interface { } ) error { return nil } ,
http . StatusOK ,
} ,
{
"ok token in headers" ,
nil ,
( func ( ) * http . Header {
token := jwt . New ( )
token . Set ( "exp" , time . Now ( ) . Add ( time . Hour ) )
keyBytes , _ := pem . Decode ( [ ] byte ( privateKey ) )
privKey , _ := x509 . ParsePKCS8PrivateKey ( keyBytes . Bytes )
b , _ := token . Sign ( jwa . RS256 , privKey )
return & http . Header {
"X-RPC-Authentication" : [ ] string { string ( b ) } ,
}
} ) ( ) ,
2019-07-09 14:50:31 +02:00
httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
w . Write ( [ ] byte (
2019-07-19 14:49:35 +02:00
fmt . Sprintf ( ` { "keys":["%s"]} ` , strings . ReplaceAll ( publicKey , "\n" , ` \n ` ) ) ,
2019-07-09 14:50:31 +02:00
) )
} ) ) ,
2019-07-16 10:29:24 +02:00
func ( r * http . Request , claims map [ string ] interface { } ) error { return nil } ,
2019-07-09 14:50:31 +02:00
http . StatusOK ,
} ,
2019-07-16 10:29:24 +02:00
{
"bad token in headers" ,
nil ,
( func ( ) * http . Header {
return & http . Header {
"X-RPC-Authentication" : [ ] string { "nilly willy" } ,
}
} ) ( ) ,
httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
w . Write ( [ ] byte (
2019-07-19 14:49:35 +02:00
fmt . Sprintf ( ` { "keys":["%s"]} ` , strings . ReplaceAll ( publicKey , "\n" , ` \n ` ) ) ,
2019-07-16 10:29:24 +02:00
) )
} ) ) ,
func ( r * http . Request , claims map [ string ] interface { } ) error { return nil } ,
http . StatusForbidden ,
} ,
{
"authorizer rejects" ,
& http . Cookie {
Name : "auth" ,
Value : ( func ( ) string {
token := jwt . New ( )
token . Set ( "exp" , time . Now ( ) . Add ( time . Hour ) )
token . Set ( "priv" , map [ string ] interface { } { "ok" : false } )
keyBytes , _ := pem . Decode ( [ ] byte ( privateKey ) )
privKey , _ := x509 . ParsePKCS8PrivateKey ( keyBytes . Bytes )
b , _ := token . Sign ( jwa . RS256 , privKey )
return string ( b )
} ) ( ) ,
} ,
nil ,
httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
w . Write ( [ ] byte (
2019-07-19 14:49:35 +02:00
fmt . Sprintf ( ` { "keys":["%s"]} ` , strings . ReplaceAll ( publicKey , "\n" , ` \n ` ) ) ,
2019-07-16 10:29:24 +02:00
) )
} ) ) ,
func ( r * http . Request , claims map [ string ] interface { } ) error {
if claims [ "ok" ] == true {
return nil
}
return errors . New ( "expected ok to be true" )
} ,
http . StatusForbidden ,
} ,
2019-07-09 14:50:31 +02:00
{
"valid key, expired token" ,
& http . Cookie {
Name : "auth" ,
Value : ( func ( ) string {
token := jwt . New ( )
token . Set ( "exp" , time . Now ( ) . Add ( - time . Hour ) )
keyBytes , _ := pem . Decode ( [ ] byte ( privateKey ) )
privKey , _ := x509 . ParsePKCS8PrivateKey ( keyBytes . Bytes )
b , _ := token . Sign ( jwa . RS256 , privKey )
return string ( b )
} ) ( ) ,
} ,
2019-07-16 10:29:24 +02:00
nil ,
2019-07-09 14:50:31 +02:00
httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
w . Write ( [ ] byte (
2019-07-19 14:49:35 +02:00
fmt . Sprintf ( ` { "keys":["%s"]} ` , strings . ReplaceAll ( publicKey , "\n" , ` \n ` ) ) ,
2019-07-09 14:50:31 +02:00
) )
} ) ) ,
2019-07-16 10:29:24 +02:00
func ( r * http . Request , claims map [ string ] interface { } ) error { return nil } ,
2019-07-09 14:50:31 +02:00
http . StatusForbidden ,
} ,
2019-07-08 15:11:06 +02:00
}
for _ , test := range tests {
t . Run ( test . name , func ( t * testing . T ) {
2019-07-09 14:50:31 +02:00
var url string
if test . server != nil {
url = test . server . URL
}
2019-07-16 10:29:24 +02:00
wrappedHandler := JWTProtect ( url , "auth" , "X-RPC-Authentication" , test . authorizer ) ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2019-07-08 15:11:06 +02:00
w . Write ( [ ] byte ( "OK" ) )
} ) )
w := httptest . NewRecorder ( )
r := httptest . NewRequest ( http . MethodGet , "/" , nil )
if test . cookie != nil {
r . AddCookie ( test . cookie )
}
2019-07-16 10:29:24 +02:00
if test . headers != nil {
for key , value := range * test . headers {
r . Header . Add ( key , value [ 0 ] )
}
}
2019-07-08 15:11:06 +02:00
wrappedHandler . ServeHTTP ( w , r )
if w . Code != test . expectedStatusCode {
t . Errorf ( "Unexpected status code %v" , w . Code )
}
} )
}
}