|
Play Framework/Scala example source code file (Algorithms.scala)
The Algorithms.scala Play Framework example source code
/*
*
* * Copyright (C) 2009-2013 Typesafe Inc. <http://www.typesafe.com>
*
*/
package play.api.libs.ws.ssl
import javax.crypto.SecretKey
import java.security.interfaces._
import javax.crypto.interfaces.DHKey
import scala.util.parsing.combinator.RegexParsers
import java.security.{ KeyFactory, Key }
import scala.Some
/**
* This singleton object provides the code needed to check for minimum standards of an X.509 certificate. Over 95% of trusted leaf certificates and 95% of trusted signing certificates use <a href="http://csrc.nist.gov/publications/nistpubs/800-131A/sp800-131A.pdf">NIST recommended key sizes</a>. Play supports Java 1.6, which does not have built in <a href="http://sim.ivi.co/2013/11/harness-ssl-and-jsse-key-size-control.html">certificate strength checking</a>, so we roll our own here.
*
* The default settings here are based off <a href="">NIST SP 800-57</a>, using <a href="https://wiki.mozilla.org/CA:MD5and1024">Dates for Phasing out MD5-based signatures and 1024-bit moduli</a> as a practical guide.
*
* Note that the key sizes are checked on root CA certificates in the trust store. As the Mozilla document says:
*
* <blockquote>The other concern that needs to be addressed is that of RSA1024 being too small a modulus to be robust against faster computers. Unlike a signature algorithm, where only intermediate and end-entity certificates are impacted, fast math means we have to disable or remove all instances of 1024-bit moduli, including the root certificates.</blockquote>
*
* Relevant key sizes:
*
* <blockquote>
* According to NIST SP 800-57 the recommended algorithms and minimum key sizes are as follows:
*
* Through 2010 (minimum of 80 bits of strength)
* FFC (e.g., DSA, D-H) Minimum: L=1024; N=160
* IFC (e.g., RSA) Minimum: k=1024
* ECC (e.g. ECDSA) Minimum: f=160
* Through 2030 (minimum of 112 bits of strength)
* FFC (e.g., DSA, D-H) Minimum: L=2048; N=224
* IFC (e.g., RSA) Minimum: k=2048
* ECC (e.g. ECDSA) Minimum: f=224
* Beyond 2030 (minimum of 128 bits of strength)
* FFC (e.g., DSA, D-H) Minimum: L=3072; N=256
* IFC (e.g., RSA) Minimum: k=3072
* ECC (e.g. ECDSA) Minimum: f=256
* </blockquote>
*
* Relevant signature algorithms:
*
* The known weak signature algorithms are "MD2, MD4, MD5".
*
* SHA-1 is considered too weak for new certificates, but is <a href="http://csrc.nist.gov/groups/ST/hash/policy.html">still allowed</a> for verifying old certificates in the chain. The <a href="https://blogs.oracle.com/xuelei/entry/tls_and_nist_s_policy">TLS and NIST'S Policy on Hash Functions</a> blog post by one of the JSSE authors has more details, in particular the "Put it into practice" section.
*/
object Algorithms {
/**
* Disabled signature algorithms are applied to signed certificates in a certificate chain, not including CA certs.
*
* @return "MD2, MD4, MD5"
*/
def disabledSignatureAlgorithms: String = "MD2, MD4, MD5"
/**
* Disabled key algorithms are applied to all certificates, including the root CAs.
*
* @return "RSA keySize < 2048, DSA keySize < 2048, EC keySize < 224"
*/
def disabledKeyAlgorithms: String = "RSA keySize < 2048, DSA keySize < 2048, EC keySize < 224"
/**
* Returns the keySize of the given key, or None if no key exists.
*/
def keySize(key: java.security.Key): Option[Int] = {
key match {
case sk: SecretKey =>
if ((sk.getFormat == "RAW") && sk.getEncoded != null) {
Some(sk.getEncoded.length * 8)
} else {
None
}
case pubk: RSAKey =>
Some(pubk.getModulus.bitLength)
case pubk: ECKey =>
Some(pubk.getParams.getOrder.bitLength())
case pubk: DSAKey =>
Some(pubk.getParams.getP.bitLength)
case pubk: DHKey =>
Some(pubk.getParams.getP.bitLength)
case pubk: Key =>
val translatedKey = translateKey(pubk)
keySize(translatedKey)
case unknownKey =>
try {
val lengthMethod = unknownKey.getClass.getMethod("length")
val l = lengthMethod.invoke(unknownKey).asInstanceOf[Integer]
if (l >= 0) Some(l) else None
} catch {
case _: Throwable =>
throw new IllegalStateException(s"unknown key ${key.getClass.getName}")
}
// None
}
}
def getKeyAlgorithmName(pubk: Key): String = {
val name = pubk.getAlgorithm
if (name == "DH") "DiffieHellman" else name
}
def translateKey(pubk: Key): Key = {
val keyAlgName = getKeyAlgorithmName(pubk)
foldVersion(
run16 = {
keyAlgName match {
case "EC" =>
// If we are on 1.6, then we can't use the EC factory and have to pull it directly.
translateECKey(pubk)
case _ =>
val keyFactory = KeyFactory.getInstance(keyAlgName)
keyFactory.translateKey(pubk)
}
},
runHigher = {
val keyFactory = KeyFactory.getInstance(keyAlgName)
keyFactory.translateKey(pubk)
}
)
}
def translateECKey(pubk: Key): Key = {
val keyFactory = Thread.currentThread().getContextClassLoader.loadClass("sun.security.ec.ECKeyFactory")
val method = keyFactory.getMethod("toECKey", classOf[java.security.Key])
method.invoke(null, pubk) match {
case e: ECPublicKey =>
e
case e: ECPrivateKey =>
e
}
}
/**
* Decompose the standard algorithm name into sub-elements.
* <p/>
* For example, we need to decompose "SHA1WithRSA" into "SHA1" and "RSA"
* so that we can check the "SHA1" and "RSA" algorithm constraints
* separately.
* <p/>
* Please override the method if need to support more name pattern.
*/
def decomposes(algorithm: String): Set[String] = {
if (algorithm == null || algorithm.length == 0) {
throw new IllegalArgumentException("Null or blank algorithm found!")
}
val withAndPattern = new scala.util.matching.Regex("(?i)with|and")
val tokens: Array[String] = "/".r.split(algorithm)
val elements = (for {
t <- tokens
name <- withAndPattern.split(t)
} yield {
name
}).toSet
if (elements.contains("SHA1") && !elements.contains("SHA-1")) {
elements + "SHA-1"
} else if (elements.contains("SHA-1") && !elements.contains("SHA1")) {
elements + "SHA1"
} else {
elements
}
}
}
sealed abstract class ExpressionSymbol {
def matches(actualKeySize: Int): Boolean
}
case class LessThan(x: Int) extends ExpressionSymbol {
def matches(actualKeySize: Int): Boolean = actualKeySize < x
override def toString = " keySize < " + x
}
case class LessThanOrEqual(x: Int) extends ExpressionSymbol {
def matches(actualKeySize: Int): Boolean = actualKeySize <= x
override def toString = " keySize <= " + x
}
case class NotEqual(x: Int) extends ExpressionSymbol {
def matches(actualKeySize: Int): Boolean = actualKeySize != x
override def toString = " keySize != " + x
}
case class Equal(x: Int) extends ExpressionSymbol {
def matches(actualKeySize: Int): Boolean = actualKeySize == x
override def toString = " keySize ==" + x
}
case class MoreThan(x: Int) extends ExpressionSymbol {
def matches(actualKeySize: Int): Boolean = actualKeySize > x
override def toString = " keySize > " + x
}
case class MoreThanOrEqual(x: Int) extends ExpressionSymbol {
def matches(actualKeySize: Int): Boolean = actualKeySize >= x
override def toString = " keySize >= " + x
}
case class AlgorithmConstraint(algorithm: String, constraint: Option[ExpressionSymbol] = None) {
/**
* Returns true only if the algorithm matches. Useful for signature algorithms where we don't care about key size.
*/
def matches(algorithm: String): Boolean = {
this.algorithm.equalsIgnoreCase(algorithm)
}
/**
* Returns true if the algorithm name matches, and if there's a keySize constraint, will match on that as well.
*/
def matches(algorithm: String, keySize: Int): Boolean = {
if (!matches(algorithm)) {
return false
}
constraint match {
case Some(expression) =>
expression.matches(keySize)
case None =>
true
}
}
override def toString = {
algorithm + constraint.getOrElse("")
}
}
/**
* Parser based on the jdk.certpath.disabledAlgorithm BNF.
*
* @see http://sim.ivi.co/2011/07/java-se-7-release-security-enhancements.html
*/
object AlgorithmConstraintsParser extends RegexParsers {
import scala.language.postfixOps
def apply(input: String): List[AlgorithmConstraint] = parseAll(line, input) match {
case Success(result, _) =>
result
case NoSuccess(message, _) =>
throw new IllegalArgumentException(s"Cannot parse string $input: $message")
}
def line: Parser[List[AlgorithmConstraint]] = repsep(expression, ",")
def expression: Parser[AlgorithmConstraint] = algorithm ~ (keySizeConstraint ?) ^^ {
case algorithm ~ Some(constraint) =>
AlgorithmConstraint(algorithm, Some(constraint))
case algorithm ~ None =>
AlgorithmConstraint(algorithm, None)
}
def keySizeConstraint: Parser[ExpressionSymbol] = "keySize" ~> operator ~ decimalInteger ^^ {
case "<=" ~ decimal =>
LessThanOrEqual(decimal)
case "<" ~ decimal =>
LessThan(decimal)
case "==" ~ decimal =>
Equal(decimal)
case "!=" ~ decimal =>
NotEqual(decimal)
case ">=" ~ decimal =>
MoreThanOrEqual(decimal)
case ">" ~ decimal =>
MoreThan(decimal)
}
def operator: Parser[String] = "<=" | "<" | "==" | "!=" | ">=" | ">"
def decimalInteger: Parser[Int] = """\d+""".r ^^ {
f => f.toInt
}
def algorithm: Parser[String] = """\w+""".r ^^ {
f => f.toString
}
}
Other Play Framework source code examplesHere is a short list of links related to this Play Framework Algorithms.scala source code file: |
| ... this post is sponsored by my books ... | |
#1 New Release! |
FP Best Seller |
Copyright 1998-2024 Alvin Alexander, alvinalexander.com
All Rights Reserved.
A percentage of advertising revenue from
pages under the /java/jwarehouse
URI on this website is
paid back to open source projects.