Adds support to read PEM keys (#29039)
Co-Authored-By: James Roper <james@jazzy.id.au>
This commit is contained in:
parent
ac3065bfad
commit
54a9b3189a
10 changed files with 478 additions and 4 deletions
130
akka-pki/src/main/scala/akka/pki/pem/DERPrivateKeyLoader.scala
Normal file
130
akka-pki/src/main/scala/akka/pki/pem/DERPrivateKeyLoader.scala
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* Copyright (C) 2020 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package akka.pki.pem
|
||||
|
||||
import java.math.BigInteger
|
||||
import java.security.KeyFactory
|
||||
import java.security.PrivateKey
|
||||
import java.security.spec.PKCS8EncodedKeySpec
|
||||
import java.security.spec.RSAMultiPrimePrivateCrtKeySpec
|
||||
import java.security.spec.RSAOtherPrimeInfo
|
||||
import java.security.spec.RSAPrivateCrtKeySpec
|
||||
|
||||
import akka.annotation.ApiMayChange
|
||||
import akka.pki.pem.PEMDecoder.DERData
|
||||
import com.hierynomus.asn1.ASN1InputStream
|
||||
import com.hierynomus.asn1.encodingrules.der.DERDecoder
|
||||
import com.hierynomus.asn1.types.constructed.ASN1Sequence
|
||||
import com.hierynomus.asn1.types.primitive.ASN1Integer
|
||||
|
||||
final class PEMLoadingException(message: String, cause: Throwable) extends RuntimeException(message, cause) {
|
||||
def this(msg: String) = this(msg, null)
|
||||
}
|
||||
|
||||
object DERPrivateKeyLoader {
|
||||
|
||||
/**
|
||||
* Converts the DER payload in [[PEMDecoder.DERData]] into a [[java.security.PrivateKey]]. The received DER
|
||||
* data must be a valid PKCS#1 (identified in PEM as "RSA PRIVATE KEY") or non-ecnrypted PKCS#8 (identified
|
||||
* in PEM as "PRIVATE KEY").
|
||||
* @throws PEMLoadingException when the `derData` is for an unsupported format
|
||||
*/
|
||||
@ApiMayChange
|
||||
@throws[PEMLoadingException]("when the `derData` is for an unsupported format")
|
||||
def load(derData: DERData): PrivateKey = {
|
||||
derData.label match {
|
||||
case "RSA PRIVATE KEY" =>
|
||||
loadPkcs1PrivateKey(derData.bytes)
|
||||
case "PRIVATE KEY" =>
|
||||
loadPkcs8PrivateKey(derData.bytes)
|
||||
case unknown =>
|
||||
throw new PEMLoadingException(s"Don't know how to read a private key from PEM data with label [$unknown]")
|
||||
}
|
||||
}
|
||||
|
||||
private def loadPkcs1PrivateKey(bytes: Array[Byte]) = {
|
||||
val derInputStream = new ASN1InputStream(new DERDecoder, bytes)
|
||||
// Here's the specification: https://tools.ietf.org/html/rfc3447#appendix-A.1.2
|
||||
val sequence = {
|
||||
try {
|
||||
derInputStream.readObject[ASN1Sequence]()
|
||||
} finally {
|
||||
derInputStream.close()
|
||||
}
|
||||
}
|
||||
val version = getInteger(sequence, 0, "version").intValueExact()
|
||||
if (version < 0 || version > 1) {
|
||||
throw new IllegalArgumentException(s"Unsupported PKCS1 version: $version")
|
||||
}
|
||||
val modulus = getInteger(sequence, 1, "modulus")
|
||||
val publicExponent = getInteger(sequence, 2, "publicExponent")
|
||||
val privateExponent = getInteger(sequence, 3, "privateExponent")
|
||||
val prime1 = getInteger(sequence, 4, "prime1")
|
||||
val prime2 = getInteger(sequence, 5, "prime2")
|
||||
val exponent1 = getInteger(sequence, 6, "exponent1")
|
||||
val exponent2 = getInteger(sequence, 7, "exponent2")
|
||||
val coefficient = getInteger(sequence, 8, "coefficient")
|
||||
|
||||
val keySpec = if (version == 0) {
|
||||
new RSAPrivateCrtKeySpec(
|
||||
modulus,
|
||||
publicExponent,
|
||||
privateExponent,
|
||||
prime1,
|
||||
prime2,
|
||||
exponent1,
|
||||
exponent2,
|
||||
coefficient)
|
||||
} else {
|
||||
// Does anyone even use multi-primes? Who knows, maybe this code will never be used. Anyway, I guess it will work,
|
||||
// the spec isn't exactly complicated.
|
||||
val otherPrimeInfosSequence = getSequence(sequence, 9, "otherPrimeInfos")
|
||||
val otherPrimeInfos = (for (i <- 0 until otherPrimeInfosSequence.size()) yield {
|
||||
val name = s"otherPrimeInfos[$i]"
|
||||
val seq = getSequence(otherPrimeInfosSequence, i, name)
|
||||
val prime = getInteger(seq, 0, s"$name.prime")
|
||||
val exponent = getInteger(seq, 1, s"$name.exponent")
|
||||
val coefficient = getInteger(seq, 2, s"$name.coefficient")
|
||||
new RSAOtherPrimeInfo(prime, exponent, coefficient)
|
||||
}).toArray
|
||||
new RSAMultiPrimePrivateCrtKeySpec(
|
||||
modulus,
|
||||
publicExponent,
|
||||
privateExponent,
|
||||
prime1,
|
||||
prime2,
|
||||
exponent1,
|
||||
exponent2,
|
||||
coefficient,
|
||||
otherPrimeInfos)
|
||||
}
|
||||
|
||||
val keyFactory = KeyFactory.getInstance("RSA")
|
||||
keyFactory.generatePrivate(keySpec)
|
||||
}
|
||||
|
||||
private def getInteger(sequence: ASN1Sequence, index: Int, name: String): BigInteger = {
|
||||
sequence.get(index) match {
|
||||
case integer: ASN1Integer => integer.getValue
|
||||
case other =>
|
||||
throw new IllegalArgumentException(s"Expected integer tag for $name at index $index, but got: ${other.getTag}")
|
||||
}
|
||||
}
|
||||
|
||||
private def getSequence(sequence: ASN1Sequence, index: Int, name: String): ASN1Sequence = {
|
||||
sequence.get(index) match {
|
||||
case seq: ASN1Sequence => seq
|
||||
case other =>
|
||||
throw new IllegalArgumentException(s"Expected sequence tag for $name at index $index, but got: ${other.getTag}")
|
||||
}
|
||||
}
|
||||
|
||||
private def loadPkcs8PrivateKey(bytes: Array[Byte]) = {
|
||||
val keySpec = new PKCS8EncodedKeySpec(bytes)
|
||||
val keyFactory = KeyFactory.getInstance("RSA")
|
||||
keyFactory.generatePrivate(keySpec)
|
||||
}
|
||||
|
||||
}
|
||||
76
akka-pki/src/main/scala/akka/pki/pem/PEMDecoder.scala
Normal file
76
akka-pki/src/main/scala/akka/pki/pem/PEMDecoder.scala
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright (C) 2020 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package akka.pki.pem
|
||||
|
||||
import java.util.Base64
|
||||
|
||||
import akka.annotation.ApiMayChange
|
||||
|
||||
/**
|
||||
* Decodes lax PEM encoded data, according to
|
||||
*
|
||||
* https://tools.ietf.org/html/rfc7468
|
||||
*/
|
||||
object PEMDecoder {
|
||||
|
||||
// I believe this regex matches the RFC7468 Lax ABNF semantics jkhdft exactly.
|
||||
private val PEMRegex = {
|
||||
// Luckily, Java Pattern's \s matches the RFCs W ABNF expression perfectly
|
||||
// (space, tab, carriage return, line feed, form feed, vertical tab)
|
||||
|
||||
// The variables here are named to match the expressions in the RFC7468 ABNF
|
||||
// description. The content of the regex may not match the structure of the
|
||||
// expression because sometimes there are nicer way to do things in regexes.
|
||||
|
||||
// All printable ASCII characters minus hyphen
|
||||
val labelchar = """[\p{Print}&&[^-]]"""
|
||||
// Starts and finishes with a labelchar, with as many label chars and hyphens or
|
||||
// spaces in between, but no double spaces or hyphens, also may be empty.
|
||||
val label = raw"""(?:$labelchar(?:[\- ]?$labelchar)*)?"""
|
||||
// capturing group so we can extract the label
|
||||
val preeb = raw"""-----BEGIN ($label)-----"""
|
||||
// we don't extract the end label because the RFC says we can ignore it (it
|
||||
// doesn't have to match the begin label)
|
||||
val posteb = raw"""-----END $label-----"""
|
||||
// Any of the base64 chars (alphanum, +, /) and whitespace, followed by at most 2
|
||||
// padding characters, separated by zero to many whitespace characters
|
||||
val laxbase64text = """[A-Za-z0-9\+/\s]*(?:=\s*){0,2}"""
|
||||
|
||||
val laxtextualmessage = raw"""\s*$preeb($laxbase64text)$posteb\s*"""
|
||||
|
||||
laxtextualmessage.r
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a PEM String into an identifier and the DER bytes of the content.
|
||||
*
|
||||
* See https://tools.ietf.org/html/rfc7468 and https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail
|
||||
*
|
||||
* @param pemData the PEM data (pre-eb, base64-MIME data and ponst-eb)
|
||||
* @return the decoded bytes and the content type.
|
||||
*/
|
||||
@throws[PEMLoadingException](
|
||||
"If the `pemData` is not valid PEM format (according to https://tools.ietf.org/html/rfc7468).")
|
||||
@ApiMayChange
|
||||
def decode(pemData: String): DERData = {
|
||||
pemData match {
|
||||
case PEMRegex(label, base64) =>
|
||||
try {
|
||||
new DERData(label, Base64.getMimeDecoder.decode(base64))
|
||||
} catch {
|
||||
case iae: IllegalArgumentException =>
|
||||
throw new PEMLoadingException(
|
||||
s"Error decoding base64 data from PEM data (note: expected MIME-formatted Base64)",
|
||||
iae)
|
||||
}
|
||||
|
||||
case _ => throw new PEMLoadingException("Not a PEM encoded data.")
|
||||
}
|
||||
}
|
||||
|
||||
@ApiMayChange
|
||||
final class DERData(val label: String, val bytes: Array[Byte])
|
||||
|
||||
}
|
||||
19
akka-pki/src/test/resources/certificate.pem
Normal file
19
akka-pki/src/test/resources/certificate.pem
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDCzCCAfOgAwIBAgIQfEHPfR1p1xuW9TQlfxAugjANBgkqhkiG9w0BAQsFADAv
|
||||
MS0wKwYDVQQDEyQwZDIwN2I2OC05YTIwLTRlZTgtOTJjYi1iZjk2OTk1ODFjZjgw
|
||||
HhcNMTkxMDExMTMyODUzWhcNMjQxMDA5MTQyODUzWjAvMS0wKwYDVQQDEyQwZDIw
|
||||
N2I2OC05YTIwLTRlZTgtOTJjYi1iZjk2OTk1ODFjZjgwggEiMA0GCSqGSIb3DQEB
|
||||
AQUAA4IBDwAwggEKAoIBAQDhD0BxlDzEOzcp45lPHL60lnM6k3puEGb2lKHL5/nR
|
||||
F94FCnZL0FH8EdxWzzAYgys+kUwSdo4QMuWuvjY2Km4Wob6k4uAeYEFTCfBdi4/z
|
||||
r4kpWzu8xLz+uZWimLQrjqVytNNK3DMv6ebWUJ/92VTDS4yzWk4YV0MVr2b2OgMK
|
||||
SgMvaFQ8L/CwyML72PBWIqU67+MMvvcTLxQdyEgQTTjP0bbiXMLDvfZDarLJojsW
|
||||
SNBz7AIkznhGkzIGGdhAa41PnPu9XaBFhaqx9Qe3+MG2/k1l/46eHtmxCqhOUde1
|
||||
i0vy6ZfgcGifua1tg1UBI/oT4S0dsq24dq7K1MYLyHTrAgMBAAGjIzAhMA4GA1Ud
|
||||
DwEB/wQEAwICBDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAa
|
||||
5YOlvob4wqps3HLaOIi7VzDihNHciP+BI0mzxHa7D3bGaecRPSeG3xEoD/Uxs9o4
|
||||
8cByPpYx1Wl8LLRx14wDcK0H+UPpo4gCI6h6q92cJj0YRjcSUUt8EIu3qnFMjtM+
|
||||
sl/uc21fGlq6cvMRZXqtVYENoSNTDHi5a5eEXRa2eZ8XntjvOanOhIKWmxvr8r4a
|
||||
Voz4WdnXx1C8/BzB62UBoMu4QqMGMLk5wXP0D6hECUuespMest+BeoJAVhTq7wZs
|
||||
rSP9q08n1stZFF4+bEBaxcqIqdhOLQdHcYELN+a5v0Mcwdsy7jJMagmNPfsKoOKC
|
||||
hLOsmNYKHdmWg37Jib5o
|
||||
-----END CERTIFICATE-----
|
||||
28
akka-pki/src/test/resources/multi-prime-pkcs1.pem
Normal file
28
akka-pki/src/test/resources/multi-prime-pkcs1.pem
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIE2AIBAQKCAQEAqFD3rwpvkqgkxSCIKyO2M///6CnJz/YqYycSxeUXJSeC+sf0
|
||||
Svu4mzSNyx9mglH6ubVJ1x01XlK7GDCAOuqi7Dgay23m+qRq+MF89gYZY9YuVPBr
|
||||
jsFPK3XguIOLTIV2VCdskBLbW0G6b6VVDOVLCffD6fRuy/H43BI2l9+nuTAdYpCE
|
||||
sX7IliRi2HR09nv1THSMrdrcq48EdLK+Qzj3f+9gedqeJP7ABY29eIBAOasUY356
|
||||
So/2dqwhfmMbXswHqIFVQIBd+y1F4Gwf4HxTN06bHhs5rOg4fAREmr1SU25CnhDA
|
||||
SBw8c5zRFNGgekkzIxSZLFw27ezYGxZyIjnKawIDAQABAoIBAFrcKniRV52BqyfG
|
||||
4fr3sjnr7gcz17+tkUApLZcqjg3+gFREcHmx3PvbqNeHwdyDyKdLV+sJ129tlZX/
|
||||
SJmFZCHEP6KlV1TiQOS7/msI69fbHPO5PTaCjTzkbA4WOAgv+M4XjR82RM1EusO1
|
||||
tPegWfLhj5dvdAfgTpodW1XDFs3QHoQbKkLLMREOb3j+LuK38npxN1UmtDNSRlII
|
||||
xv77KywOij0LMG4CjIeXmjGdL9BWzlbHv5Zk0wuWHtGFvkoQO6EPn3NVb1uY1ZdX
|
||||
IekdvA71qssliVFi30/3ZiFTwt3fXfNBawN7t1s0pJlsPAOz7iiucHWrUCvtaXD+
|
||||
miFFN4ECVgez3BQqe6o3lmHG7LQKy/RWBfDXLCc1JziipTeu+e1piNd7WnJcpsbl
|
||||
31kTA3JBp7VU41EEXEBtWz8cW3I9y6AhukAyeWBnww9FSet8bKGAuYj1siCXAlYH
|
||||
L+pEeS8NuGO1iigEEZzZrOMC3Yg9/evwhjXxbtwst9NewE22UdJGB7tm/2v7osaE
|
||||
/DVQk4xrNe/AEDRcQf73XXEEq/wsoJjHp/Be8lvH6bLHpx0rBQJWA6Lxy3+HJQTb
|
||||
dcwx4oqaYf/fDCa6TMR2gPGg6RouGt1RYtJ1EDsX7h2HBgnI/b9ri1vJem2BlFVM
|
||||
2A3uSpBMO4zQ3xA1Z8N1PxdqX8g9jT412iiNcX8CVWC3atQH8iuwKhnSEqyuVw7s
|
||||
e/kTMVcIsQMNENzn+/3JwaBQNXZFnGLJqhdhIMAkuVbcopiu9+/E4462gecCNRSX
|
||||
XUyBHANxJIWicAtDYdXaDP6u3NUCVgSPOp+63cxSzMo7FLsYIAFH14VFEXKaw9s9
|
||||
JbMI4+dIUMfgs2fvCK6ibFpIrhESv4kaQ+uRjkTjyGv+OkKvP5jxoDUjKRA4WcLU
|
||||
OHH2wrajfrFJ0sfhMIIBDDCCAQgCVgMKWFv+kAwf55zhJjiP/+dSyL7wnEFw5gcw
|
||||
F8tBg1M7LR4BgTn+j/p/8qEg4scL0eDdDcxGNPaMNlYAtFjO56Qv/oAHJ7EdwfYR
|
||||
knMUDxYZV2LU+aGpAlYCMhBIrnWbK9b3xOby5ZnolDF/IQXVhA+4lRQ5pR+OlScp
|
||||
ifClzpxuSsMNdFAPaQuwlDEImJJakDoUtQGHODKysC3ailAxaMnORjY5f/y8+qPO
|
||||
LPnvsQJWAYm7meCYk9a89TGWQfFl4l1W1dYlI/4FDYOirOvFd+/LICMk+9E43Qff
|
||||
lqeAL45/QoXNEDpSVNy507ggtBbxqcEehjspYGqp76NRTvRMKgK9/XH4u2E=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
27
akka-pki/src/test/resources/pkcs1.pem
Normal file
27
akka-pki/src/test/resources/pkcs1.pem
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEA0IQFs9KpS6fpm7Bq5JcAzRzdZnYub7qGBJ9+QZX8F6qUYiXf
|
||||
jbVPAZSIksNg6G5yMkzBLZ8r0UOm5WJs6sAHKGJOQk9DcwZEt3XpGbYXAnM5V1sb
|
||||
xd5oNcbXLcHouU8jrEj+O2KvmgCzDDCUOf3SjGnF4dWqhsTT9tMJZvWVB0OjpKnT
|
||||
zcoxFKO/XCz8Spb1+FgKUgt3afA+JTRpQWtaZJ41fuTg0rm0qtUSeZXPUEYkqqK4
|
||||
msoSe7dbu7uiEOkUPoeiP7wzSrwihQSCxOzwdphV2XtF5Xfs24/Ad4WKXTqRVUK/
|
||||
yldcQy2orFSsX41KBzS8PI1hnOC6uRA0PiGd/QIDAQABAoIBAQCDbxShGuK326m3
|
||||
B2b5m+1XXSB5m3j92FbtxxMwiDgVOuK5UyItEuIwHs5PpHQLTsMQzaze8vwNtlUX
|
||||
Ngltl4lrfTvTNF9Ru9vIwLwkBtFOLA8y7yz8doq9iw7LuvTVCft0d7Y4/KWvr00t
|
||||
G9nzC/mRpIKlLaeFt7/cT34XtikwH+Ez8uWGidYnrqkKZ0bsIZvD+e7gae+veohN
|
||||
BnTcyDIk8P5nXG0vM4hxtjLo3KstemwOt6vCtiKzL2Vq/JAVD3nlec8WPBzft79I
|
||||
k5tb3Qm/OnxIQaWF5AhAVkXgMsLL3ddJoAn/K6NZ6NtRGZozkwdP+m4nacrKFJVJ
|
||||
6ew7OdAJAoGBAO65GvteHLXQ3d1NfU+X6RSBatJdpuf2S4Hc7P+sTkPjLDlQPDKL
|
||||
ZFLVigPlCehMvgGASPOxL8UqPZugwEo83ywp5t/iOUccXZCPSISreSzWJPfOJ+dl
|
||||
aKP2cSHHPNC/mISDpp/NF+xfgEAUQQ6AdOKwHGlsFWBvkkw810d4zmXvAoGBAN+b
|
||||
QYv32wNa2IHC1Ri3VgtfHpQ20fMz+UO6TMefBbgkcgBk4L+O7gSCpThaqy9R7UnX
|
||||
IEH0QyaBEXzQxB7qvCo1pczWiEUcG+vAyyz9U9oO9Hee/UTMOWL4Sj7qoavau2Be
|
||||
5PFOO6qA+19JTnStuNb3swNrMmxDQpyNDvUkYAbTAoGBALuYkSCJ84vZaBBZrajX
|
||||
mt13WieYWuocPXf+0euVTyfAJOehKr0ZlywVDNFEssVvUT1Cv5FpYz3QlPtwlsuA
|
||||
DGzbPMghMZu1Kb3JK1a+nYnjeseVpPwNT+7RYlQGCr+MYOF5x336oNsqrVEt2XX4
|
||||
8mGVva4GtsHCy7fHc/GBeMjXAoGBALhEYkytER//okG0xBUdKFwwo6tyTavEndpx
|
||||
UUqDwpvP9N5cQ1W4vG6dFviMx1s0gX4DOQMA/sFhRX79L1FnEW8bTKmz9RI2qs+p
|
||||
zgUiMhKVlmJpc79ZKMVlZRHaGybbFuTA7pvoY4ULy5rndy7x5kvITg44LZJID0Gh
|
||||
gL0Fn9ifAoGAEaWA7yxTA7phDIt0U91HZEVhNSM4cZDurE7614qWhKveSP8f0J4c
|
||||
3d9y/re4RcCwmss/FtQWSkB+32WbD3qk+QB7hV1oFqJ5ObcfwevGYR8m6vOz1h2L
|
||||
3pQNi4PcH3U8eeGG1laFKUQ295rBLNqIbOo2y+4hxMnC4tka1X118Ec=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
28
akka-pki/src/test/resources/pkcs8.pem
Normal file
28
akka-pki/src/test/resources/pkcs8.pem
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDQhAWz0qlLp+mb
|
||||
sGrklwDNHN1mdi5vuoYEn35BlfwXqpRiJd+NtU8BlIiSw2DobnIyTMEtnyvRQ6bl
|
||||
YmzqwAcoYk5CT0NzBkS3dekZthcCczlXWxvF3mg1xtctwei5TyOsSP47Yq+aALMM
|
||||
MJQ5/dKMacXh1aqGxNP20wlm9ZUHQ6OkqdPNyjEUo79cLPxKlvX4WApSC3dp8D4l
|
||||
NGlBa1pknjV+5ODSubSq1RJ5lc9QRiSqoriayhJ7t1u7u6IQ6RQ+h6I/vDNKvCKF
|
||||
BILE7PB2mFXZe0Xld+zbj8B3hYpdOpFVQr/KV1xDLaisVKxfjUoHNLw8jWGc4Lq5
|
||||
EDQ+IZ39AgMBAAECggEBAINvFKEa4rfbqbcHZvmb7VddIHmbeP3YVu3HEzCIOBU6
|
||||
4rlTIi0S4jAezk+kdAtOwxDNrN7y/A22VRc2CW2XiWt9O9M0X1G728jAvCQG0U4s
|
||||
DzLvLPx2ir2LDsu69NUJ+3R3tjj8pa+vTS0b2fML+ZGkgqUtp4W3v9xPfhe2KTAf
|
||||
4TPy5YaJ1ieuqQpnRuwhm8P57uBp7696iE0GdNzIMiTw/mdcbS8ziHG2Mujcqy16
|
||||
bA63q8K2IrMvZWr8kBUPeeV5zxY8HN+3v0iTm1vdCb86fEhBpYXkCEBWReAywsvd
|
||||
10mgCf8ro1no21EZmjOTB0/6bidpysoUlUnp7Ds50AkCgYEA7rka+14ctdDd3U19
|
||||
T5fpFIFq0l2m5/ZLgdzs/6xOQ+MsOVA8MotkUtWKA+UJ6Ey+AYBI87EvxSo9m6DA
|
||||
SjzfLCnm3+I5RxxdkI9IhKt5LNYk984n52Voo/ZxIcc80L+YhIOmn80X7F+AQBRB
|
||||
DoB04rAcaWwVYG+STDzXR3jOZe8CgYEA35tBi/fbA1rYgcLVGLdWC18elDbR8zP5
|
||||
Q7pMx58FuCRyAGTgv47uBIKlOFqrL1HtSdcgQfRDJoERfNDEHuq8KjWlzNaIRRwb
|
||||
68DLLP1T2g70d579RMw5YvhKPuqhq9q7YF7k8U47qoD7X0lOdK241vezA2sybENC
|
||||
nI0O9SRgBtMCgYEAu5iRIInzi9loEFmtqNea3XdaJ5ha6hw9d/7R65VPJ8Ak56Eq
|
||||
vRmXLBUM0USyxW9RPUK/kWljPdCU+3CWy4AMbNs8yCExm7UpvckrVr6dieN6x5Wk
|
||||
/A1P7tFiVAYKv4xg4XnHffqg2yqtUS3ZdfjyYZW9rga2wcLLt8dz8YF4yNcCgYEA
|
||||
uERiTK0RH/+iQbTEFR0oXDCjq3JNq8Sd2nFRSoPCm8/03lxDVbi8bp0W+IzHWzSB
|
||||
fgM5AwD+wWFFfv0vUWcRbxtMqbP1Ejaqz6nOBSIyEpWWYmlzv1koxWVlEdobJtsW
|
||||
5MDum+hjhQvLmud3LvHmS8hODjgtkkgPQaGAvQWf2J8CgYARpYDvLFMDumEMi3RT
|
||||
3UdkRWE1IzhxkO6sTvrXipaEq95I/x/Qnhzd33L+t7hFwLCayz8W1BZKQH7fZZsP
|
||||
eqT5AHuFXWgWonk5tx/B68ZhHybq87PWHYvelA2Lg9wfdTx54YbWVoUpRDb3msEs
|
||||
2ohs6jbL7iHEycLi2RrVfXXwRw==
|
||||
-----END PRIVATE KEY-----
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (C) 2020 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package akka.pki.pem
|
||||
|
||||
import java.io.File
|
||||
import java.nio.charset.Charset
|
||||
import java.nio.file.Files
|
||||
import java.security.PrivateKey
|
||||
|
||||
import org.scalatest.EitherValues
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
import org.scalatest.wordspec.AnyWordSpec
|
||||
|
||||
class DERPrivateKeyLoaderSpec extends AnyWordSpec with Matchers with EitherValues {
|
||||
|
||||
"The DER Private Key loader" should {
|
||||
"decode the same key in PKCS#1 and PKCS#8 formats" in {
|
||||
val pkcs1 = load("pkcs1.pem")
|
||||
val pkcs8 = load("pkcs8.pem")
|
||||
pkcs1 should ===(pkcs8)
|
||||
}
|
||||
|
||||
"parse multi primes" in {
|
||||
load("multi-prime-pkcs1.pem")
|
||||
// Not much we can verify here - I actually think the default JDK security implementation ignores the extra
|
||||
// primes, and it fails to parse a multi-prime PKCS#8 key.
|
||||
}
|
||||
|
||||
"fail on unsupported PEM contents (Certificates are not private keys)" in {
|
||||
assertThrows[PEMLoadingException] {
|
||||
load("certificate.pem")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private def load(resource: String): PrivateKey = {
|
||||
val derData: PEMDecoder.DERData = loadDerData(resource)
|
||||
DERPrivateKeyLoader.load(derData)
|
||||
}
|
||||
|
||||
private def loadDerData(resource: String) = {
|
||||
val resourceUrl = getClass.getClassLoader.getResource(resource)
|
||||
resourceUrl.getProtocol should ===("file")
|
||||
val path = new File(resourceUrl.toURI).toPath
|
||||
val bytes = Files.readAllBytes(path)
|
||||
val str = new String(bytes, Charset.forName("UTF-8"))
|
||||
val derData = PEMDecoder.decode(str)
|
||||
derData
|
||||
}
|
||||
|
||||
}
|
||||
93
akka-pki/src/test/scala/akka/pki/pem/PEMDecoderSpec.scala
Normal file
93
akka-pki/src/test/scala/akka/pki/pem/PEMDecoderSpec.scala
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* Copyright (C) 2020 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package akka.pki.pem
|
||||
|
||||
import java.util.Base64
|
||||
|
||||
import org.scalatest.EitherValues
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
import org.scalatest.wordspec.AnyWordSpec
|
||||
|
||||
class PEMDecoderSpec extends AnyWordSpec with Matchers with EitherValues {
|
||||
|
||||
private val cert =
|
||||
"""-----BEGIN CERTIFICATE-----
|
||||
|MIIDCzCCAfOgAwIBAgIQfEHPfR1p1xuW9TQlfxAugjANBgkqhkiG9w0BAQsFADAv
|
||||
|MS0wKwYDVQQDEyQwZDIwN2I2OC05YTIwLTRlZTgtOTJjYi1iZjk2OTk1ODFjZjgw
|
||||
|HhcNMTkxMDExMTMyODUzWhcNMjQxMDA5MTQyODUzWjAvMS0wKwYDVQQDEyQwZDIw
|
||||
|N2I2OC05YTIwLTRlZTgtOTJjYi1iZjk2OTk1ODFjZjgwggEiMA0GCSqGSIb3DQEB
|
||||
|AQUAA4IBDwAwggEKAoIBAQDhD0BxlDzEOzcp45lPHL60lnM6k3puEGb2lKHL5/nR
|
||||
|F94FCnZL0FH8EdxWzzAYgys+kUwSdo4QMuWuvjY2Km4Wob6k4uAeYEFTCfBdi4/z
|
||||
|r4kpWzu8xLz+uZWimLQrjqVytNNK3DMv6ebWUJ/92VTDS4yzWk4YV0MVr2b2OgMK
|
||||
|SgMvaFQ8L/CwyML72PBWIqU67+MMvvcTLxQdyEgQTTjP0bbiXMLDvfZDarLJojsW
|
||||
|SNBz7AIkznhGkzIGGdhAa41PnPu9XaBFhaqx9Qe3+MG2/k1l/46eHtmxCqhOUde1
|
||||
|i0vy6ZfgcGifua1tg1UBI/oT4S0dsq24dq7K1MYLyHTrAgMBAAGjIzAhMA4GA1Ud
|
||||
|DwEB/wQEAwICBDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAa
|
||||
|5YOlvob4wqps3HLaOIi7VzDihNHciP+BI0mzxHa7D3bGaecRPSeG3xEoD/Uxs9o4
|
||||
|8cByPpYx1Wl8LLRx14wDcK0H+UPpo4gCI6h6q92cJj0YRjcSUUt8EIu3qnFMjtM+
|
||||
|sl/uc21fGlq6cvMRZXqtVYENoSNTDHi5a5eEXRa2eZ8XntjvOanOhIKWmxvr8r4a
|
||||
|Voz4WdnXx1C8/BzB62UBoMu4QqMGMLk5wXP0D6hECUuespMest+BeoJAVhTq7wZs
|
||||
|rSP9q08n1stZFF4+bEBaxcqIqdhOLQdHcYELN+a5v0Mcwdsy7jJMagmNPfsKoOKC
|
||||
|hLOsmNYKHdmWg37Jib5o
|
||||
|-----END CERTIFICATE-----""".stripMargin
|
||||
|
||||
"The PEM decoder" should {
|
||||
"decode a real world certificate" in {
|
||||
PEMDecoder.decode(cert).label should ===("CERTIFICATE")
|
||||
}
|
||||
|
||||
"decode data with no spaces" in {
|
||||
val result = PEMDecoder.decode(
|
||||
"-----BEGIN FOO-----" + Base64.getEncoder.encodeToString("abc".getBytes()) + "-----END FOO-----")
|
||||
result.label should ===("FOO")
|
||||
new String(result.bytes) should ===("abc")
|
||||
}
|
||||
|
||||
"decode data with lots of spaces" in {
|
||||
val result = PEMDecoder.decode(
|
||||
"\n \t \r -----BEGIN FOO-----\n" +
|
||||
Base64.getEncoder.encodeToString("abc".getBytes()).flatMap(c => s"$c\n\r \t\n") + "-----END FOO-----\n \t \r ")
|
||||
result.label should ===("FOO")
|
||||
new String(result.bytes) should ===("abc")
|
||||
}
|
||||
|
||||
"decode data with two padding characters" in {
|
||||
// A 4 byte input results in a 6 character output with 2 padding characters
|
||||
val result = PEMDecoder.decode(
|
||||
"-----BEGIN FOO-----" + Base64.getEncoder.encodeToString("abcd".getBytes()) + "-----END FOO-----")
|
||||
result.label should ===("FOO")
|
||||
new String(result.bytes) should ===("abcd")
|
||||
}
|
||||
|
||||
"decode data with one padding character" in {
|
||||
// A 5 byte input results in a 7 character output with 1 padding character1
|
||||
val result = PEMDecoder.decode(
|
||||
"-----BEGIN FOO-----" + Base64.getEncoder.encodeToString("abcde".getBytes()) + "-----END FOO-----")
|
||||
result.label should ===("FOO")
|
||||
new String(result.bytes) should ===("abcde")
|
||||
}
|
||||
|
||||
"fail decode when the format is wrong (not MIME BASE64, lines too long)" in {
|
||||
val input = """-----BEGIN CERTIFICATE-----
|
||||
|MIIDCzCCAfOgAwIBAgIQfEHPfR1p1xuW9TQlfxAugjANBgkqhkiG9w0BAQsFADAviZjk2OTk1ODFjZjgw
|
||||
|HhcNMTkxMDExMTMyODUzWhcNMjQxMDA5MTQyODUzWjAvMS0wKwYDVQQDEyQwZDIwhLOsmNYKHdmWg37Jib5o
|
||||
|-----END CERTIFICATE-----""".stripMargin
|
||||
|
||||
assertThrows[PEMLoadingException] {
|
||||
PEMDecoder.decode(input)
|
||||
}
|
||||
}
|
||||
|
||||
"fail decode when the format is wrong (not PEM, invalid per/post-EB)" in {
|
||||
val input = cert.replace("BEGIN", "BGN").replace("END ", "GLGLGL ")
|
||||
|
||||
assertThrows[PEMLoadingException] {
|
||||
PEMDecoder.decode(input)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
18
build.sbt
18
build.sbt
|
|
@ -1,4 +1,4 @@
|
|||
import akka.{AutomaticModuleName, CopyrightHeaderForBuild, Paradox, ScalafixIgnoreFilePlugin}
|
||||
import akka.{ AutomaticModuleName, CopyrightHeaderForBuild, Paradox, ScalafixIgnoreFilePlugin }
|
||||
|
||||
enablePlugins(
|
||||
UnidocRoot,
|
||||
|
|
@ -11,17 +11,18 @@ enablePlugins(
|
|||
disablePlugins(MimaPlugin)
|
||||
addCommandAlias(
|
||||
name = "fixall",
|
||||
value = ";scalafixEnable;compile:scalafix;test:scalafix;multi-jvm:scalafix;scalafmtAll;test:compile;multi-jvm:compile;reload")
|
||||
value =
|
||||
";scalafixEnable;compile:scalafix;test:scalafix;multi-jvm:scalafix;scalafmtAll;test:compile;multi-jvm:compile;reload")
|
||||
|
||||
addCommandAlias(
|
||||
name = "sortImports",
|
||||
value = ";scalafixEnable;compile:scalafix SortImports;test:scalafix SortImports;scalafmtAll")
|
||||
|
||||
import akka.AkkaBuild._
|
||||
import akka.{AkkaBuild, Dependencies, OSGi, Protobuf, SigarLoader, VersionGenerator}
|
||||
import akka.{ AkkaBuild, Dependencies, OSGi, Protobuf, SigarLoader, VersionGenerator }
|
||||
import com.typesafe.sbt.SbtMultiJvm.MultiJvmKeys.MultiJvm
|
||||
import com.typesafe.tools.mima.plugin.MimaPlugin
|
||||
import sbt.Keys.{initialCommands, parallelExecution}
|
||||
import sbt.Keys.{ initialCommands, parallelExecution }
|
||||
import spray.boilerplate.BoilerplatePlugin
|
||||
|
||||
initialize := {
|
||||
|
|
@ -67,6 +68,7 @@ lazy val aggregatedProjects: Seq[ProjectReference] = List[ProjectReference](
|
|||
persistenceTestkit,
|
||||
protobuf,
|
||||
protobufV3,
|
||||
pki,
|
||||
remote,
|
||||
remoteTests,
|
||||
slf4j,
|
||||
|
|
@ -313,6 +315,14 @@ lazy val protobufV3 = akkaModule("akka-protobuf-v3")
|
|||
test in assembly := {}, // assembly runs tests for unknown reason which introduces another cyclic dependency to packageBin via exportedJars
|
||||
description := "Akka Protobuf V3 is a shaded version of the protobuf runtime. Original POM: https://github.com/protocolbuffers/protobuf/blob/v3.9.0/java/pom.xml")
|
||||
|
||||
lazy val pki =
|
||||
akkaModule("akka-pki")
|
||||
.dependsOn(actor) // this dependency only exists for "@ApiMayChange"
|
||||
.settings(Dependencies.pki)
|
||||
.settings(AutomaticModuleName.settings("akka.pki"))
|
||||
// The akka-pki artifact was added in Akka 2.6.2, no MiMa checks yet.
|
||||
.disablePlugins(MimaPlugin)
|
||||
|
||||
lazy val remote =
|
||||
akkaModule("akka-remote")
|
||||
.dependsOn(
|
||||
|
|
|
|||
|
|
@ -83,6 +83,8 @@ object Dependencies {
|
|||
// Added explicitly for when artery tcp is used
|
||||
val agrona = "org.agrona" % "agrona" % agronaVersion // ApacheV2
|
||||
|
||||
val asnOne = "com.hierynomus" % "asn-one" % "0.4.0" // ApacheV2
|
||||
|
||||
val jacksonCore = "com.fasterxml.jackson.core" % "jackson-core" % jacksonVersion // ApacheV2
|
||||
val jacksonAnnotations = "com.fasterxml.jackson.core" % "jackson-annotations" % jacksonVersion // ApacheV2
|
||||
val jacksonDatabind = "com.fasterxml.jackson.core" % "jackson-databind" % jacksonVersion // ApacheV2
|
||||
|
|
@ -195,6 +197,13 @@ object Dependencies {
|
|||
|
||||
val actorTestkitTyped = l ++= Seq(Provided.logback, Provided.junit, Provided.scalatest, Test.scalatestJUnit)
|
||||
|
||||
val pki = l ++=
|
||||
Seq(
|
||||
asnOne,
|
||||
// pull up slf4j version from the one provided transitively in asnOne to fix unidoc
|
||||
Compile.slf4jApi % "provided",
|
||||
Test.scalatest)
|
||||
|
||||
val remoteDependencies = Seq(netty, aeronDriver, aeronClient)
|
||||
val remoteOptionalDependencies = remoteDependencies.map(_ % "optional")
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue