Commit 89a9152f authored by Sietse Ringers's avatar Sietse Ringers
Browse files

feat: enforce https for all appropriate http connections

parent 83219f43
......@@ -17,6 +17,11 @@ import (
var Logger *logrus.Logger
// Disables HTTP forcing in irma.HTTPTransport for all instances,
// regardless of the instance's ForceHTTPS member.
// Only for use in unit tests.
var ForceHTTPS = true
// AssertPathExists returns nil only if it has been successfully
// verified that all specified paths exists.
func AssertPathExists(paths ...string) error {
......
......@@ -12,6 +12,7 @@ import (
jwt "github.com/dgrijalva/jwt-go"
irma "github.com/privacybydesign/irmago"
"github.com/privacybydesign/irmago/internal/common"
"github.com/privacybydesign/irmago/internal/test"
"github.com/privacybydesign/irmago/irmaclient"
"github.com/privacybydesign/irmago/server"
......@@ -20,7 +21,7 @@ import (
)
func init() {
irma.ForceHttps = false
common.ForceHTTPS = false // globally disable https enforcement
irma.Logger.SetLevel(logrus.WarnLevel)
}
......@@ -136,19 +137,19 @@ func startSession(t *testing.T, request irma.SessionRequest, sessiontype string)
switch TestType {
case "apiserver":
url := "http://localhost:8088/irma_api_server/api/v2/" + sessiontype
err = irma.NewHTTPTransport(url).Post("", qr, getJwt(t, request, sessiontype, jwt.SigningMethodNone))
err = irma.NewHTTPTransport(url, false).Post("", qr, getJwt(t, request, sessiontype, jwt.SigningMethodNone))
qr.URL = url + "/" + qr.URL
case "irmaserver-jwt":
url := "http://localhost:48682"
err = irma.NewHTTPTransport(url).Post("session", &sesPkg, getJwt(t, request, sessiontype, jwt.SigningMethodRS256))
err = irma.NewHTTPTransport(url, false).Post("session", &sesPkg, getJwt(t, request, sessiontype, jwt.SigningMethodRS256))
qr = sesPkg.SessionPtr
case "irmaserver-hmac-jwt":
url := "http://localhost:48682"
err = irma.NewHTTPTransport(url).Post("session", &sesPkg, getJwt(t, request, sessiontype, jwt.SigningMethodHS256))
err = irma.NewHTTPTransport(url, false).Post("session", &sesPkg, getJwt(t, request, sessiontype, jwt.SigningMethodHS256))
qr = sesPkg.SessionPtr
case "irmaserver":
url := "http://localhost:48682"
err = irma.NewHTTPTransport(url).Post("session", &sesPkg, request)
err = irma.NewHTTPTransport(url, false).Post("session", &sesPkg, request)
qr = sesPkg.SessionPtr
default:
t.Fatal("Invalid TestType")
......
......@@ -10,6 +10,7 @@ import (
"time"
"github.com/privacybydesign/irmago"
"github.com/privacybydesign/irmago/internal/common"
"github.com/privacybydesign/irmago/internal/test"
"github.com/privacybydesign/irmago/irmaclient"
"github.com/privacybydesign/irmago/server"
......@@ -126,7 +127,7 @@ func TestRequestorDoubleGET(t *testing.T) {
// Simulate the first GET by the client in the session protocol, twice
var o interface{}
transport := irma.NewHTTPTransport(qr.URL)
transport := irma.NewHTTPTransport(qr.URL, false)
transport.SetHeader(irma.MinVersionHeader, "2.5")
transport.SetHeader(irma.MaxVersionHeader, "2.5")
require.NoError(t, transport.Get("", &o))
......@@ -354,6 +355,8 @@ func TestOptionalDisclosure(t *testing.T) {
}
func TestClientDeveloperMode(t *testing.T) {
common.ForceHTTPS = true
defer func() { common.ForceHTTPS = false }()
client, handler := parseStorage(t)
defer test.ClearTestStorage(t, handler.storage)
StartIrmaServer(t, false)
......@@ -385,6 +388,6 @@ func TestClientDeveloperMode(t *testing.T) {
serr, ok := result.Err.(*irma.SessionError)
require.True(t, ok)
require.NotNil(t, serr)
require.Equal(t, irma.ErrorInvalidRequest, serr.ErrorType)
require.Equal(t, string(irma.ErrorHTTPS), string(serr.ErrorType))
require.Equal(t, "remote server does not use https", serr.Err.Error())
}
......@@ -51,7 +51,7 @@ func postRevocation(request *irma.RevocationRequest, url, schemespath, authmetho
die("credential type does not support revocation", nil)
}
transport := irma.NewHTTPTransport(url)
transport := irma.NewHTTPTransport(url, false)
switch authmethod {
case "none":
......
......@@ -180,7 +180,7 @@ func postRequest(serverurl string, request irma.RequestorRequest, name, authmeth
var (
err error
pkg = &server.SessionPackage{}
transport = irma.NewHTTPTransport(serverurl)
transport = irma.NewHTTPTransport(serverurl, false)
)
switch authmethod {
......
......@@ -998,7 +998,7 @@ func (client *Client) keyshareEnrollWorker(managerID irma.SchemeManagerIdentifie
return errors.New("PIN too short, must be at least 5 characters")
}
transport := irma.NewHTTPTransport(manager.KeyshareServer)
transport := irma.NewHTTPTransport(manager.KeyshareServer, !client.Preferences.DeveloperMode)
kss, err := newKeyshareServer(managerID)
if err != nil {
return err
......@@ -1043,7 +1043,9 @@ func (client *Client) KeyshareVerifyPin(pin string, schemeid irma.SchemeManagerI
}
}
kss := client.keyshareServers[schemeid]
return verifyPinWorker(pin, kss, irma.NewHTTPTransport(scheme.KeyshareServer))
return verifyPinWorker(pin, kss,
irma.NewHTTPTransport(scheme.KeyshareServer, !client.Preferences.DeveloperMode),
)
}
func (client *Client) KeyshareChangePin(manager irma.SchemeManagerIdentifier, oldPin string, newPin string) {
......@@ -1061,7 +1063,7 @@ func (client *Client) keyshareChangePinWorker(managerID irma.SchemeManagerIdenti
return errors.New("Unknown keyshare server")
}
transport := irma.NewHTTPTransport(client.Configuration.SchemeManagers[managerID].KeyshareServer)
transport := irma.NewHTTPTransport(client.Configuration.SchemeManagers[managerID].KeyshareServer, !client.Preferences.DeveloperMode)
message := keyshareChangepin{
Username: kss.Username,
OldPin: kss.HashedPin(oldPin),
......@@ -1126,14 +1128,17 @@ func (client *Client) LoadLogsBefore(beforeIndex uint64, max int) ([]*LogEntry,
}
func (client *Client) SetDeveloperMode(enable bool) {
if enable {
irma.Logger.Info("developer mode enabled")
} else {
irma.Logger.Info("developer mode disabled")
}
client.Preferences.DeveloperMode = enable
_ = client.storage.StorePreferences(client.Preferences)
client.applyPreferences()
}
func (client *Client) applyPreferences() {
irma.ForceHttps = !client.Preferences.DeveloperMode
}
func (client *Client) applyPreferences() {}
// ConfigurationUpdated should be run after Configuration.Download().
// For any credential type in the updated scheme to which new attributes were added, this function
......
......@@ -9,6 +9,7 @@ import (
"github.com/privacybydesign/gabi"
irma "github.com/privacybydesign/irmago"
"github.com/privacybydesign/irmago/internal/common"
"github.com/privacybydesign/irmago/internal/test"
"github.com/sirupsen/logrus"
......@@ -19,6 +20,7 @@ import (
func TestMain(m *testing.M) {
irma.Logger.SetLevel(logrus.FatalLevel)
common.ForceHTTPS = false // globally disable https enforcement
// Create HTTP server for scheme managers
test.StartSchemeManagerHttpServer()
......
......@@ -51,6 +51,7 @@ type keyshareSession struct {
issuerProofNonce *big.Int
timestamp *atum.Timestamp
pinCheck bool
preferences Preferences
}
type keyshareServer struct {
......@@ -153,10 +154,11 @@ func startKeyshareSession(
pin KeysharePinRequestor,
builders gabi.ProofBuilderList,
session irma.SessionRequest,
conf *irma.Configuration,
keyshareServers map[irma.SchemeManagerIdentifier]*keyshareServer,
issuerProofNonce *big.Int,
timestamp *atum.Timestamp,
conf *irma.Configuration,
keyshareServers map[irma.SchemeManagerIdentifier]*keyshareServer,
preferences Preferences,
) {
ksscount := 0
for managerID := range session.Identifiers().SchemeManagers {
......@@ -186,6 +188,7 @@ func startKeyshareSession(
issuerProofNonce: issuerProofNonce,
timestamp: timestamp,
pinCheck: false,
preferences: preferences,
}
for managerID := range session.Identifiers().SchemeManagers {
......@@ -195,7 +198,7 @@ func startKeyshareSession(
}
ks.keyshareServer = ks.keyshareServers[managerID]
transport := irma.NewHTTPTransport(scheme.KeyshareServer)
transport := irma.NewHTTPTransport(scheme.KeyshareServer, !ks.preferences.DeveloperMode)
transport.SetHeader(kssUsernameHeader, ks.keyshareServer.Username)
transport.SetHeader(kssAuthHeader, "Bearer "+ks.keyshareServer.token)
transport.SetHeader(kssVersionHeader, "2")
......
......@@ -166,7 +166,8 @@ func (client *Client) newManualSession(request irma.SessionRequest, handler Hand
func (client *Client) newQrSession(qr *irma.Qr, handler Handler) SessionDismisser {
if qr.Type == irma.ActionRedirect {
newqr := &irma.Qr{}
if err := irma.NewHTTPTransport("").Post(qr.URL, newqr, struct{}{}); err != nil {
transport := irma.NewHTTPTransport("", !client.Preferences.DeveloperMode)
if err := transport.Post(qr.URL, newqr, struct{}{}); err != nil {
handler.Failure(&irma.SessionError{ErrorType: irma.ErrorTransport, Err: errors.Wrap(err, 0)})
return nil
}
......@@ -183,8 +184,8 @@ func (client *Client) newQrSession(qr *irma.Qr, handler Handler) SessionDismisse
session := &session{
ServerURL: qr.URL,
Hostname: u.Hostname(),
transport: irma.NewHTTPTransport(qr.URL),
Action: irma.Action(qr.Type),
transport: irma.NewHTTPTransport(qr.URL, !client.Preferences.DeveloperMode),
Action: qr.Type,
Handler: handler,
client: client,
prepRevocation: make(chan error),
......@@ -402,10 +403,11 @@ func (session *session) doSession(proceed bool, choice *irma.DisclosureChoice) {
session.Handler,
session.builders,
session.request,
session.client.Configuration,
session.client.keyshareServers,
session.issuerProofNonce,
session.timestamp,
session.client.Configuration,
session.client.keyshareServers,
session.client.Preferences,
)
}
}
......
......@@ -720,7 +720,7 @@ func (conf *Configuration) CopyManagerFromAssets(scheme SchemeManagerIdentifier)
// DownloadSchemeManager downloads and returns a scheme manager description.xml file
// from the specified URL.
func DownloadSchemeManager(url string) (*SchemeManager, error) {
if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") {
if !strings.HasPrefix(url, "https://") {
url = "https://" + url
}
if url[len(url)-1] == '/' {
......@@ -729,7 +729,7 @@ func DownloadSchemeManager(url string) (*SchemeManager, error) {
if strings.HasSuffix(url, "/description.xml") {
url = url[:len(url)-len("/description.xml")]
}
b, err := NewHTTPTransport(url).GetBytes("description.xml")
b, err := NewHTTPTransport(url, true).GetBytes("description.xml")
if err != nil {
return nil, err
}
......@@ -786,7 +786,7 @@ func (conf *Configuration) installSchemeManager(manager *SchemeManager, publicke
if err := common.EnsureDirectoryExists(filepath.Join(conf.Path, name)); err != nil {
return err
}
t := NewHTTPTransport(manager.URL)
t := NewHTTPTransport(manager.URL, true)
if publickey != nil {
if err := common.SaveFile(filepath.Join(conf.Path, name, "pk.pem"), publickey); err != nil {
......@@ -819,7 +819,7 @@ func (conf *Configuration) DownloadSchemeManagerSignature(manager *SchemeManager
return errors.New("cannot download into a read-only configuration")
}
t := NewHTTPTransport(manager.URL)
t := NewHTTPTransport(manager.URL, true)
if err = conf.downloadFile(t, manager.ID, "index"); err != nil {
return
}
......@@ -1224,7 +1224,7 @@ func (conf *Configuration) UpdateSchemeManager(id SchemeManagerIdentifier, downl
}
// Check remote timestamp, verify it against the new index, and see if we have to do anything
transport := NewHTTPTransport(manager.URL + "/")
transport := NewHTTPTransport(manager.URL+"/", true)
err = conf.downloadSignedFile(transport, manager.ID, "timestamp", newIndex[manager.ID+"/timestamp"])
if err != nil {
return err
......
......@@ -16,6 +16,10 @@ import (
"github.com/stretchr/testify/require"
)
func init() {
common.ForceHTTPS = false // globally disable https enforcement
}
func parseConfiguration(t *testing.T) *Configuration {
conf, err := NewConfiguration("testdata/irma_configuration", ConfigurationOptions{})
require.NoError(t, err)
......@@ -69,7 +73,7 @@ func TestRetryHTTPRequest(t *testing.T) {
test.StartBadHttpServer(2, 1*time.Second, "42")
defer test.StopBadHttpServer()
transport := NewHTTPTransport("http://localhost:48682")
transport := NewHTTPTransport("http://localhost:48682", false)
transport.client.HTTPClient.Timeout = 500 * time.Millisecond
bts, err := transport.GetBytes("")
require.NoError(t, err)
......
......@@ -18,8 +18,6 @@ import (
// Status encodes the status of an IRMA session (e.g., connected).
type Status string
var ForceHttps = true
const (
MinVersionHeader = "X-IRMA-MinProtocolVersion"
MaxVersionHeader = "X-IRMA-MaxProtocolVersion"
......@@ -189,6 +187,8 @@ const (
ErrorProtocolVersionNotSupported = ErrorType("protocolVersionNotSupported")
// Error in HTTP communication
ErrorTransport = ErrorType("transport")
// HTTPS required
ErrorHTTPS = ErrorType("https")
// Invalid client JWT in first IRMA message
ErrorInvalidJWT = ErrorType("invalidJwt")
// Unkown session type (not disclosing, signing, or issuing)
......@@ -338,13 +338,9 @@ func (qr *Qr) Validate() (err error) {
if qr.URL == "" {
return errors.New("no URL specified")
}
var u *url.URL
if u, err = url.ParseRequestURI(qr.URL); err != nil {
if _, err = url.ParseRequestURI(qr.URL); err != nil {
return errors.Errorf("invalid URL: %s", err.Error())
}
if ForceHttps && u.Scheme != "https" {
return errors.Errorf("remote server does not use https")
}
if !qr.IsQr() {
return errors.New("unsupported session type")
}
......
......@@ -857,7 +857,7 @@ func (client RevocationClient) PostIssuanceRecord(id CredentialTypeIdentifier, s
if err != nil {
return err
}
return client.transport().Post(
return client.transport(false).Post(
fmt.Sprintf("%s/revocation/%s/issuancerecord/%d", url, id, sk.Counter), nil, []byte(message),
)
}
......@@ -954,7 +954,7 @@ func (client RevocationClient) FetchUpdatesLatest(id CredentialTypeIdentifier, c
func (client RevocationClient) getMultiple(urls []string, path string, dest interface{}) error {
var (
errs multierror.Error
transport = client.transport()
transport = client.transport(true)
)
for _, url := range urls {
transport.Server = url
......@@ -968,9 +968,9 @@ func (client RevocationClient) getMultiple(urls []string, path string, dest inte
return &errs
}
func (client RevocationClient) transport() *HTTPTransport {
func (client RevocationClient) transport(forceHTTPS bool) *HTTPTransport {
if client.http == nil {
client.http = NewHTTPTransport("")
client.http = NewHTTPTransport("", forceHTTPS)
client.http.Binary = true
}
return client.http
......
......@@ -57,7 +57,7 @@ func (conf *Configuration) downloadDemoPrivateKeys(scheme *SchemeManager) error
}
Logger.Debugf("Attempting downloading of private keys of scheme %s", scheme.ID)
transport := NewHTTPTransport(scheme.URL)
transport := NewHTTPTransport(scheme.URL, true)
err := conf.downloadFile(transport, scheme.ID, "sk.pem")
if err != nil { // If downloading of any of the private key fails just log it, and then continue
......
......@@ -335,7 +335,7 @@ func DoResultCallback(callbackUrl string, result *SessionResult, issuer string,
}
var x string // dummy for the server's return value that we don't care about
if err := irma.NewHTTPTransport(callbackUrl).Post("", &x, res); err != nil {
if err := irma.NewHTTPTransport(callbackUrl, false).Post("", &x, res); err != nil {
// not our problem, log it and go on
logger.Warn(errors.WrapPrefix(err, "Failed to POST session result to callback URL", 0))
}
......
......@@ -389,7 +389,7 @@ func (conf *Configuration) verifyEmail() error {
if !strings.Contains(conf.Email, "@") || strings.Contains(conf.Email, "\n") {
return errors.New("Invalid email address specified")
}
t := irma.NewHTTPTransport("https://metrics.privacybydesign.foundation/history")
t := irma.NewHTTPTransport("https://metrics.privacybydesign.foundation/history", true)
t.SetHeader("User-Agent", "irmaserver")
var x string
_ = t.Post("email", &x, conf.Email)
......
......@@ -14,6 +14,7 @@ import (
"strings"
"time"
"github.com/go-errors/errors"
"github.com/hashicorp/go-retryablehttp"
"github.com/privacybydesign/gabi"
"github.com/privacybydesign/gabi/revocation"
......@@ -26,10 +27,11 @@ import (
// HTTPTransport sends and receives JSON messages to a HTTP server.
type HTTPTransport struct {
Server string
Binary bool
client *retryablehttp.Client
headers http.Header
Server string
Binary bool
ForceHTTPS bool
client *retryablehttp.Client
headers http.Header
}
var HTTPHeaders = map[string]http.Header{}
......@@ -57,7 +59,7 @@ func SetLogger(logger *logrus.Logger) {
}
// NewHTTPTransport returns a new HTTPTransport.
func NewHTTPTransport(serverURL string) *HTTPTransport {
func NewHTTPTransport(serverURL string, forceHTTPS bool) *HTTPTransport {
if Logger.IsLevelEnabled(logrus.TraceLevel) {
transportlogger = log.New(Logger.WriterLevel(logrus.TraceLevel), "transport: ", 0)
} else {
......@@ -110,9 +112,10 @@ func NewHTTPTransport(serverURL string) *HTTPTransport {
headers = http.Header{}
}
return &HTTPTransport{
Server: serverURL,
headers: headers,
client: client,
Server: serverURL,
ForceHTTPS: forceHTTPS,
headers: headers,
client: client,
}
}
......@@ -168,7 +171,11 @@ func (transport *HTTPTransport) request(
url string, method string, reader io.Reader, contenttype string,
) (response *http.Response, err error) {
var req retryablehttp.Request
req.Request, err = http.NewRequest(method, transport.Server+url, reader)
u := transport.Server + url
if common.ForceHTTPS && transport.ForceHTTPS && !strings.HasPrefix(u, "https") {
return nil, &SessionError{ErrorType: ErrorHTTPS, Err: errors.New("remote server does not use https")}
}
req.Request, err = http.NewRequest(method, u, reader)
if err != nil {
return nil, &SessionError{ErrorType: ErrorTransport, Err: err}
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment