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 = `
2019-07-20 16:27:56 +02:00
-- -- - BEGIN RSA PUBLIC KEY -- -- -
MIIBCgKCAQEAl2ifzOsh6AMRHZBe8xycvk01s3EAGT12WbgV9Z7b420Dj3NXrkns
N / jvBbtO9cjQg4WM7NPZLs + ZutRkHCtMxt7vB0kjOYetLPcGdObsVBB5k1jvwvsJ
HkcfmSsZdrV0Lz2Yxuf6ADWkxBqAY3GsS0zW0A2nIMc + 41 ZxqsZa3ProsKJxecRX
SSZMpZtqCGt / S83Rek4eAahllcWfZpQmoEk7usLuUl5tH2TmaW3e5lo0JNfdwcq5
PMCa8WSZBFH3YzVttB8rbe7a7336wL2NJQFz6dswL5X1dECYpZ5TRtNgzQYa4V0W
AeICq + EzigaTxrjDHc5urHqEosz1le7O4QIDAQAB
-- -- - END RSA PUBLIC KEY -- -- -
2019-07-09 14:50:31 +02:00
`
const privateKey = `
-- -- - BEGIN PRIVATE KEY -- -- -
2019-07-20 16:27:56 +02:00
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXaJ / M6yHoAxEd
kF7zHJy + TTWzcQAZPXZZuBX1ntvjbQOPc1euSew3 + O8Fu071yNCDhYzs09kuz5m6
1 GQcK0zG3u8HSSM5h60s9wZ05uxUEHmTWO / C + wkeRx + ZKxl2tXQvPZjG5 / oANaTE
GoBjcaxLTNbQDacgxz7jVnGqxlrc + uiwonF5xFdJJkylm2oIa39LzdF6Th4BqGWV
xZ9mlCagSTu6wu5SXm0fZOZpbd7mWjQk193Byrk8wJrxZJkEUfdjNW20Hytt7trv
ffrAvY0lAXPp2zAvlfV0QJilnlNG02DNBhrhXRYB4gKr4TOKBpPGuMMdzm6seoSi
zPWV7s7hAgMBAAECggEAYoPOxjSP4ThtoIDZZvHNAv2V3WW / HK0jHolqsGBmznmW
AXaZLGwo6NpuG5qea8n38jupUEcfXxfw / OFJKhL6Z8OSX3k1FC + 1 fDZW2yWNy7zU
fg02I / XXHv5EDxM + BEFYkYxQpcs2nYBJ7tcXhpzl8DDU7JaVkfxSbPVIDEf3wyP2
k6DjYEeAj7uAsp50 / 32 H9zhlJP / cFZaPiyFYy9 / gOmDenrPVyJ / f7iQYNwYAwdbt
yfp11Wd5BpePR58 + YXICE8oBtzHvB50akK6RZULC3xHVxLQQ1bSxx6vnttxw5RW +
QRHTVWtRyWiKe / l5jMvVSUo5XCLqsL2iXfR4bz6hyQKBgQDJQVWEGHyD6JyaN / 6i
5 M5 + O / YTbzMBgt1JAuVR2c0HYE4LpgrX4cA4kT3Bsa / Z9o0uuWVuxVab5gLsjsDu
EI4o + HJQ3pl4LF9xqrndTdybwmZAT6jv3rM / VGfaCDzXCPzVx169I + WsfkyGW7Tr
Cj4KDZyykruG / 9 OrpN1Aeq9a3wKBgQDAmCnQHLEPvZdezJGBc34HjZntrbW67iFB
L0waCGWydyunYmzfja1FSvlSoziZdqoq0N4 + uBQFPZIlERvq0zMgIzNFvxt / WlFV
kQcBV24MNa8dtd + P7GDY8TzTfYBeXwJoi5c59sWLzSwpTlw0rI + ZvuA66eEF7xih
eWw3k1jOPwKBgQCKf8DHGEbQTEtBQlmlZkrIyqDs / PCgEJwSe8Cu1HFpqxfqokkC
CiTLiQB0BMEdAbRlPEcWtQ2GWgMXIqKY8qGyhk + 9 YYNCFV9VjQU9zDCOrHjLt0Zu
VNcMNR0HCfY8kb3VrM + A4GxVidFGAWR + / 9 xz9KwqpBoTrIjRrbJphkSZBwKBgBMs
0 zTqNmLH0JNasL3 / vrOH0KSOYAKdhOgVinEpFt7 + 6 HTA4vAbDf5RKaOlppP48ZZT
t1ztPOkMqUlRe8MUhgmUF53BGj7CwkhPqS / kAYvrqGS / 3 + NXeIkA87pmy2oZ8YZx
J3xY6nAx3Ey8hYelCqMXEwIqmQHbPUuOaEzcOcJHAoGANw0TRha2YSbUqS3HiGR /
Jm0lNfeLc3cYlEq0ggRDPLD11485rxVKnaKVHGPYW28OQ4jA + GCc7VZCfPV8VQXW
6 b + jUnnBwu / KuYvGMee / xJv1c4MTG54mR9UrUt + R80S0OplpcYkcQnft2Bi + AZ1h
5 aZTE7XIouXCYiMKPl4AMtI =
2019-07-09 14:50:31 +02:00
-- -- - 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 )
}
} )
}
}