From 78ad0c63d3773f55c44c96f7e80269e9279453bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Mikl=C3=B3s?= Date: Thu, 25 Feb 2016 22:09:32 +0100 Subject: [PATCH] add Strict-Transport-Security header #19861 fix the signature of Strict-Transport-Security header #19861 parse strict-transport-security header with the max-age directive only #19861 parse includeSubDomains directive #19861 add doc on Stict-Transport-Security #19861 fix punctuation in doc on Stict-Transport-Security #19861 --- .../rst/scala/http/common/http-model.rst | 10 +++++++++ .../headers/StrictTransportSecurity.java | 21 +++++++++++++++++++ .../http/impl/model/parser/HeaderParser.scala | 1 + .../impl/model/parser/SimpleHeaders.scala | 4 ++++ .../http/scaladsl/model/headers/headers.scala | 13 ++++++++++++ .../impl/model/parser/HttpHeaderSpec.scala | 6 ++++++ 6 files changed, 55 insertions(+) create mode 100644 akka-http-core/src/main/java/akka/http/javadsl/model/headers/StrictTransportSecurity.java diff --git a/akka-docs/rst/scala/http/common/http-model.rst b/akka-docs/rst/scala/http/common/http-model.rst index 74cc530875..af13c5342c 100644 --- a/akka-docs/rst/scala/http/common/http-model.rst +++ b/akka-docs/rst/scala/http/common/http-model.rst @@ -282,6 +282,16 @@ Connection request's method, protocol and potential ``Connection`` header as well as the response's protocol, entity and potential ``Connection`` header. See `this test`__ for a full table of what happens when. +Strict-Transport-Security + HTTP Strict Transport Security (HSTS) is a web security policy mechanism which is communicated by the + ``Strict-Transport-Security`` header. The most important security vulnerability that HSTS can fix is SSL-stripping + man-in-the-middle attacks. The SSL-stripping attact works by transparently converting a secure HTTPS connection into a + plain HTTP connection. The user can see that the connection is insecure, but crucially there is no way of knowing + whether the connection should be secure. HSTS addresses this problem by informing the browser that connections to the + site should always use TLS/SSL. See also `RFC 6797`_. + +.. _RFC 6797: http://tools.ietf.org/html/rfc6797 + __ @github@/akka-http-core/src/test/scala/akka/http/impl/engine/rendering/ResponseRendererSpec.scala#L422 Custom Headers diff --git a/akka-http-core/src/main/java/akka/http/javadsl/model/headers/StrictTransportSecurity.java b/akka-http-core/src/main/java/akka/http/javadsl/model/headers/StrictTransportSecurity.java new file mode 100644 index 0000000000..c0322d9c7d --- /dev/null +++ b/akka-http-core/src/main/java/akka/http/javadsl/model/headers/StrictTransportSecurity.java @@ -0,0 +1,21 @@ +/** + * Copyright (C) 2009-2016 Lightbend Inc. + */ + +package akka.http.javadsl.model.headers; + +/** + * Model for the `Strict-Transport-Security` header. + * Specification: https://tools.ietf.org/html/rfc6797 + */ +public abstract class StrictTransportSecurity extends akka.http.scaladsl.model.HttpHeader { + public abstract long maxAge(); + public abstract boolean includeSubDomains(); + + public static StrictTransportSecurity create(long maxAge) { + return new akka.http.scaladsl.model.headers.Strict$minusTransport$minusSecurity(maxAge, false); + } + public static StrictTransportSecurity create(long maxAge, boolean includeSubDomains) { + return new akka.http.scaladsl.model.headers.Strict$minusTransport$minusSecurity(maxAge, includeSubDomains); + } +} diff --git a/akka-http-core/src/main/scala/akka/http/impl/model/parser/HeaderParser.scala b/akka-http-core/src/main/scala/akka/http/impl/model/parser/HeaderParser.scala index dcc0bcc846..0bc2792f56 100644 --- a/akka-http-core/src/main/scala/akka/http/impl/model/parser/HeaderParser.scala +++ b/akka-http-core/src/main/scala/akka/http/impl/model/parser/HeaderParser.scala @@ -150,6 +150,7 @@ private[http] object HeaderParser { "sec-websocket-protocol", "sec-websocket-version", "set-cookie", + "strict-transport-security", "transfer-encoding", "upgrade", "user-agent", diff --git a/akka-http-core/src/main/scala/akka/http/impl/model/parser/SimpleHeaders.scala b/akka-http-core/src/main/scala/akka/http/impl/model/parser/SimpleHeaders.scala index 320e501be8..d7d2ee72e2 100644 --- a/akka-http-core/src/main/scala/akka/http/impl/model/parser/SimpleHeaders.scala +++ b/akka-http-core/src/main/scala/akka/http/impl/model/parser/SimpleHeaders.scala @@ -186,6 +186,10 @@ private[parser] trait SimpleHeaders { this: Parser with CommonRules with CommonA // http://tools.ietf.org/html/rfc7231#section-7.4.2 def server = rule { products ~ EOI ~> (Server(_)) } + def `strict-transport-security` = rule { + ignoreCase("max-age=") ~ `delta-seconds` ~ optional(ws(";") ~ ignoreCase("includesubdomains") ~ push(true)) ~ EOI ~> (`Strict-Transport-Security`(_, _)) + } + // http://tools.ietf.org/html/rfc7230#section-3.3.1 def `transfer-encoding` = rule { oneOrMore(`transfer-coding`).separatedBy(listSep) ~ EOI ~> (`Transfer-Encoding`(_)) diff --git a/akka-http-core/src/main/scala/akka/http/scaladsl/model/headers/headers.scala b/akka-http-core/src/main/scala/akka/http/scaladsl/model/headers/headers.scala index 172e6f9a50..1e8822d7d9 100644 --- a/akka-http-core/src/main/scala/akka/http/scaladsl/model/headers/headers.scala +++ b/akka-http-core/src/main/scala/akka/http/scaladsl/model/headers/headers.scala @@ -791,6 +791,19 @@ final case class Server(products: immutable.Seq[ProductVersion]) extends jm.head def getProducts: Iterable[jm.headers.ProductVersion] = products.asJava } +// https://tools.ietf.org/html/rfc6797 +object `Strict-Transport-Security` extends ModeledCompanion[`Strict-Transport-Security`] { + def apply(maxAge: Long, includeSubDomains: Option[Boolean]) = new `Strict-Transport-Security`(maxAge, includeSubDomains.getOrElse(false)) +} +final case class `Strict-Transport-Security`(maxAge: Long, includeSubDomains: Boolean = false) extends jm.headers.StrictTransportSecurity with ResponseHeader { + def renderValue[R <: Rendering](r: R): r.type = { + r ~~ "max-age=" ~~ maxAge + if (includeSubDomains) r ~~ "; includeSubDomains" + r + } + protected def companion = `Strict-Transport-Security` +} + // https://tools.ietf.org/html/rfc6265 object `Set-Cookie` extends ModeledCompanion[`Set-Cookie`] final case class `Set-Cookie`(cookie: HttpCookie) extends jm.headers.SetCookie with ResponseHeader { diff --git a/akka-http-core/src/test/scala/akka/http/impl/model/parser/HttpHeaderSpec.scala b/akka-http-core/src/test/scala/akka/http/impl/model/parser/HttpHeaderSpec.scala index 9a2ac59a0d..38153160dc 100644 --- a/akka-http-core/src/test/scala/akka/http/impl/model/parser/HttpHeaderSpec.scala +++ b/akka-http-core/src/test/scala/akka/http/impl/model/parser/HttpHeaderSpec.scala @@ -386,6 +386,12 @@ class HttpHeaderSpec extends FreeSpec with Matchers { "Server: as fghf.fdf/xx" =!= `Server`(Vector(ProductVersion("as"), ProductVersion("fghf.fdf", "xx"))) } + "Strict-Transport-Security" in { + "Strict-Transport-Security: max-age=31536000" =!= `Strict-Transport-Security`(maxAge = 31536000) + "Strict-Transport-Security: max-age=31536000" =!= `Strict-Transport-Security`(maxAge = 31536000, includeSubDomains = false) + "Strict-Transport-Security: max-age=31536000; includeSubDomains" =!= `Strict-Transport-Security`(maxAge = 31536000, includeSubDomains = true) + } + "Transfer-Encoding" in { "Transfer-Encoding: chunked" =!= `Transfer-Encoding`(TransferEncodings.chunked) "Transfer-Encoding: gzip" =!= `Transfer-Encoding`(TransferEncodings.gzip)