Merge pull request #26 from kuba2k2/feature/hebe-jvm-update
[hebe-jvm] Refactor signature and certificate/keypair generators
This commit is contained in:
commit
a99ca50a31
5 changed files with 162 additions and 93 deletions
|
@ -16,22 +16,38 @@ dependencies {
|
|||
}
|
||||
```
|
||||
|
||||
Additionally, to use the library on Android, [Core Library Desugaring](https://developer.android.com/studio/write/java8-support) has to be enabled.
|
||||
|
||||
## Usage
|
||||
|
||||
Generate an RSA2048 key pair (private key and certificate):
|
||||
Generate an RSA2048 key pair:
|
||||
```kotlin
|
||||
import io.github.wulkanowy.signer.hebe.android.generateKeyPair
|
||||
|
||||
val (certificate, fingerprint, privateKey) = generateKeyPair()
|
||||
val (publicPem, privatePem, publicHash) = generateKeyPair()
|
||||
```
|
||||
|
||||
Sign request content:
|
||||
Generate a certificate:
|
||||
```kotlin
|
||||
import io.github.wulkanowy.signer.hebe.android.getSignatureValues
|
||||
import io.github.wulkanowy.signer.hebe.android.generateKeyPair
|
||||
|
||||
val (digest, canonicalUrl, signature) = getSignatureValues(fingerprint, privateKey, body, fullUrl, Date())
|
||||
val (certificatePem, certificateHash) = generateCertificate(privatePem)
|
||||
```
|
||||
|
||||
### Sign request content
|
||||
```kotlin
|
||||
import io.github.wulkanowy.signer.hebe.android.getSignatureHeaders
|
||||
|
||||
val headers = getSignatureHeaders(keyId, privatePem, body, fullUrl, ZonedDateTime.now())
|
||||
```
|
||||
|
||||
The `keyId` depends on the `CertificateType` (sent in the registration request JSON):
|
||||
- for `X509` - SHA-1 of the raw certificate bytes (`certificateHash`)
|
||||
- for `RSA_PEM` - MD5 of the PEM-encoded public key (`publicHash`)
|
||||
|
||||
Hashes are represented as hexadecimal strings, without spaces.
|
||||
PEM encoding is considered as Base64 here, without wrapping or RSA headers.
|
||||
|
||||
## Tests
|
||||
|
||||
```bash
|
||||
|
|
54
hebe-jvm/src/main/kotlin/generator.kt
Normal file
54
hebe-jvm/src/main/kotlin/generator.kt
Normal file
|
@ -0,0 +1,54 @@
|
|||
package io.github.wulkanowy.signer.hebe
|
||||
|
||||
import com.migcomponents.migbase64.Base64
|
||||
import eu.szkolny.x509.X509Generator
|
||||
import java.security.KeyFactory
|
||||
import java.security.KeyPair
|
||||
import java.security.KeyPairGenerator
|
||||
import java.security.MessageDigest
|
||||
import java.security.interfaces.RSAPrivateCrtKey
|
||||
import java.security.spec.PKCS8EncodedKeySpec
|
||||
import java.security.spec.RSAPublicKeySpec
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
fun generateKeyPair(): Triple<String, String, String> {
|
||||
val generator = KeyPairGenerator.getInstance("RSA")
|
||||
generator.initialize(2048)
|
||||
val keyPair = generator.generateKeyPair()
|
||||
val publicKey = keyPair.public.encoded
|
||||
val privateKey = keyPair.private.encoded
|
||||
|
||||
val publicPem = Base64.encodeToString(publicKey, false)
|
||||
val privatePem = Base64.encodeToString(privateKey, false)
|
||||
val publicHash = MessageDigest.getInstance("MD5")
|
||||
.digest(publicPem.toByteArray())
|
||||
.joinToString("") { "%02x".format(it) }
|
||||
return Triple(publicPem, privatePem, publicHash)
|
||||
}
|
||||
|
||||
fun generateCertificate(privatePem: String): Pair<String, String> {
|
||||
val keyFactory = KeyFactory.getInstance("RSA")
|
||||
val privateBytes = Base64.decode(privatePem)
|
||||
val privateSpec = PKCS8EncodedKeySpec(privateBytes)
|
||||
val privateKey = keyFactory.generatePrivate(privateSpec) as RSAPrivateCrtKey
|
||||
val publicSpec = RSAPublicKeySpec(privateKey.modulus, privateKey.publicExponent, privateKey.params)
|
||||
val publicKey = keyFactory.generatePublic(publicSpec)
|
||||
val keyPair = KeyPair(publicKey, privateKey)
|
||||
|
||||
val notBefore = ZonedDateTime.now()
|
||||
val notAfter = notBefore.plusYears(20)
|
||||
|
||||
val cert = X509Generator(X509Generator.Algorithm.RSA_SHA256)
|
||||
.generate(subject = mapOf("CN" to "APP_CERTIFICATE CA Certificate"),
|
||||
notBefore = notBefore,
|
||||
notAfter = notAfter,
|
||||
serialNumber = 1,
|
||||
keyPair = keyPair
|
||||
)
|
||||
|
||||
val certificatePem = Base64.encodeToString(cert, false)
|
||||
val certificateHash = MessageDigest.getInstance("SHA-1")
|
||||
.digest(cert)
|
||||
.joinToString("") { "%02x".format(it) }
|
||||
return Pair(certificatePem, certificateHash)
|
||||
}
|
|
@ -1,32 +1,31 @@
|
|||
package io.github.wulkanowy.signer.hebe
|
||||
|
||||
import com.migcomponents.migbase64.Base64
|
||||
import eu.szkolny.x509.X509Generator
|
||||
import java.net.URLEncoder
|
||||
import java.security.KeyFactory
|
||||
import java.security.KeyPairGenerator
|
||||
import java.security.Signature
|
||||
import java.security.spec.PKCS8EncodedKeySpec
|
||||
import java.text.SimpleDateFormat
|
||||
import java.time.ZoneId
|
||||
import java.time.ZonedDateTime
|
||||
import java.util.*
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.security.MessageDigest.getInstance as createSign
|
||||
|
||||
private fun getDigest(body: String?): String {
|
||||
if (body == null) return ""
|
||||
private fun getDigest(body: String?): String? {
|
||||
if (body == null) return null
|
||||
return Base64.encodeToString(createSign("SHA-256").digest(body.toByteArray()), false)
|
||||
}
|
||||
|
||||
private fun getSignatureValue(values: String, privateKey: String): String {
|
||||
val bl = Base64.decode(privateKey)
|
||||
val spec = PKCS8EncodedKeySpec(bl)
|
||||
val kf = KeyFactory.getInstance("RSA")
|
||||
private fun getSignatureValue(values: String, privatePem: String): String {
|
||||
val keyFactory = KeyFactory.getInstance("RSA")
|
||||
val privateBytes = Base64.decode(privatePem)
|
||||
val privateSpec = PKCS8EncodedKeySpec(privateBytes)
|
||||
val privateKey = keyFactory.generatePrivate(privateSpec)
|
||||
|
||||
val privateSignature = Signature.getInstance("SHA256withRSA")
|
||||
privateSignature.initSign(kf.generatePrivate(spec))
|
||||
privateSignature.update(values.toByteArray())
|
||||
val signature = Signature.getInstance("SHA256withRSA")
|
||||
signature.initSign(privateKey)
|
||||
signature.update(values.toByteArray())
|
||||
|
||||
return Base64.encodeToString(privateSignature.sign(), false)
|
||||
return Base64.encodeToString(signature.sign(), false)
|
||||
}
|
||||
|
||||
private fun getEncodedPath(path: String): String {
|
||||
|
@ -36,60 +35,30 @@ private fun getEncodedPath(path: String): String {
|
|||
return URLEncoder.encode(url.groupValues[0], "UTF-8").orEmpty().toLowerCase()
|
||||
}
|
||||
|
||||
private fun getHeadersList(body: String?, digest: String, canonicalUrl: String, timestamp: Date): Pair<String, String> {
|
||||
val signData = mutableMapOf<String, String>()
|
||||
signData["vCanonicalUrl"] = canonicalUrl
|
||||
if (body != null) signData["Digest"] = digest
|
||||
signData["vDate"] = SimpleDateFormat("EEE, d MMM yyyy hh:mm:ss z", Locale.ENGLISH).apply {
|
||||
timeZone = TimeZone.getTimeZone("GMT")
|
||||
}.format(timestamp)
|
||||
|
||||
return Pair(
|
||||
first = signData.keys.joinToString(" "),
|
||||
second = signData.values.joinToString("")
|
||||
)
|
||||
private fun getHeaders(digest: String?, canonicalUrl: String, timestamp: ZonedDateTime): MutableMap<String, String> {
|
||||
val headers = mutableMapOf<String, String>()
|
||||
headers["vCanonicalUrl"] = canonicalUrl
|
||||
if (digest != null) headers["Digest"] = digest
|
||||
headers["vDate"] = timestamp.format(DateTimeFormatter.RFC_1123_DATE_TIME)
|
||||
return headers
|
||||
}
|
||||
|
||||
fun getSignatureValues(
|
||||
fingerprint: String,
|
||||
privateKey: String,
|
||||
fun getSignatureHeaders(
|
||||
keyId: String,
|
||||
privatePem: String,
|
||||
body: String?,
|
||||
requestPath: String,
|
||||
timestamp: Date
|
||||
): Triple<String, String, String> {
|
||||
timestamp: ZonedDateTime
|
||||
): Map<String, String> {
|
||||
val canonicalUrl = getEncodedPath(requestPath)
|
||||
val digest = getDigest(body)
|
||||
val (headers, values) = getHeadersList(body, digest, canonicalUrl, timestamp)
|
||||
val signatureValue = getSignatureValue(values, privateKey)
|
||||
val headers = getHeaders(digest, canonicalUrl, timestamp.withZoneSameInstant(ZoneId.of("GMT")))
|
||||
val headerNames = headers.keys.joinToString(" ")
|
||||
val headerValues = headers.values.joinToString("")
|
||||
val signatureValue = getSignatureValue(headerValues, privatePem)
|
||||
|
||||
return Triple(
|
||||
"SHA-256=${digest}",
|
||||
canonicalUrl,
|
||||
"""keyId="$fingerprint",headers="$headers",algorithm="sha256withrsa",signature=Base64(SHA256withRSA($signatureValue))"""
|
||||
)
|
||||
}
|
||||
|
||||
fun generateKeyPair(): Triple<String, String, String> {
|
||||
val generator = KeyPairGenerator.getInstance("RSA")
|
||||
generator.initialize(2048)
|
||||
val keyPair = generator.generateKeyPair()
|
||||
val privateKey = keyPair.private
|
||||
|
||||
val notBefore = ZonedDateTime.now()
|
||||
val notAfter = notBefore.plusYears(20)
|
||||
|
||||
val cert = X509Generator(X509Generator.Algorithm.RSA_SHA256)
|
||||
.generate(subject = mapOf("CN" to "APP_CERTIFICATE CA Certificate"),
|
||||
notBefore = notBefore,
|
||||
notAfter = notAfter,
|
||||
serialNumber = 1,
|
||||
keyPair = keyPair
|
||||
)
|
||||
|
||||
val certificatePem = Base64.encodeToString(cert, false)
|
||||
val fingerprint = createSign("SHA-1")
|
||||
.digest(cert)
|
||||
.joinToString("") { "%02x".format(it) }
|
||||
val privateKeyPem = Base64.encodeToString(privateKey.encoded, false)
|
||||
return Triple(certificatePem, fingerprint, privateKeyPem)
|
||||
if (body != null) headers["Digest"] = "SHA-256=${digest}"
|
||||
headers["Signature"] = """keyId="$keyId,headers="$headerNames",algorithm="sha256",signature=Base64(sha256withrsa($signatureValue))"""
|
||||
|
||||
return headers
|
||||
}
|
||||
|
|
|
@ -9,30 +9,45 @@ import java.security.cert.CertificateFactory
|
|||
import java.security.interfaces.RSAPrivateCrtKey
|
||||
import java.security.spec.PKCS8EncodedKeySpec
|
||||
import java.security.spec.RSAPublicKeySpec
|
||||
import java.security.spec.X509EncodedKeySpec
|
||||
import java.util.*
|
||||
|
||||
class GeneratorTest {
|
||||
|
||||
@Test
|
||||
fun generatorTest() {
|
||||
val (certificate, fingerprint, privateKey) = generateKeyPair()
|
||||
val (publicPem, privatePem, publicHash) = generateKeyPair()
|
||||
val (certificatePem, certificateHash) = generateCertificate(privatePem)
|
||||
|
||||
val certificateFactory = CertificateFactory.getInstance("X.509")
|
||||
val x509 = certificateFactory.generateCertificate(
|
||||
ByteArrayInputStream(Base64.getDecoder().decode(certificate))
|
||||
ByteArrayInputStream(Base64.getDecoder().decode(certificatePem))
|
||||
)
|
||||
|
||||
val keyFactory = KeyFactory.getInstance("RSA")
|
||||
val pkcs8KeySpec = PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey))
|
||||
val private = keyFactory.generatePrivate(pkcs8KeySpec) as RSAPrivateCrtKey
|
||||
val rsaKeySpec = RSAPublicKeySpec(private.modulus, private.publicExponent, private.params)
|
||||
val publicKey = keyFactory.generatePublic(rsaKeySpec)
|
||||
val privateBytes = Base64.getDecoder().decode(privatePem)
|
||||
val privateSpec = PKCS8EncodedKeySpec(privateBytes)
|
||||
val privateKey = keyFactory.generatePrivate(privateSpec) as RSAPrivateCrtKey
|
||||
val publicSpec = RSAPublicKeySpec(privateKey.modulus, privateKey.publicExponent, privateKey.params)
|
||||
val publicKey = keyFactory.generatePublic(publicSpec)
|
||||
|
||||
val digest = MessageDigest.getInstance("SHA-1")
|
||||
digest.update(x509.encoded)
|
||||
val publicSpec2 = X509EncodedKeySpec(Base64.getDecoder().decode(publicPem))
|
||||
val publicKey2 = keyFactory.generatePublic(publicSpec2)
|
||||
|
||||
assertEquals(fingerprint.length, 40)
|
||||
assertEquals(digest.digest().joinToString("") { "%02x".format(it) }, fingerprint)
|
||||
val sha1 = MessageDigest.getInstance("SHA-1")
|
||||
val md5 = MessageDigest.getInstance("MD5")
|
||||
|
||||
assertEquals(certificateHash.length, 40)
|
||||
assertEquals(sha1.digest(x509.encoded).joinToString("") { "%02x".format(it) }, certificateHash)
|
||||
assertEquals(x509.publicKey, publicKey)
|
||||
assertEquals(publicKey, publicKey2)
|
||||
x509.verify(publicKey2)
|
||||
assertEquals(x509.type, "X.509")
|
||||
assertEquals(x509.publicKey.algorithm, "RSA")
|
||||
assertEquals(md5.digest(
|
||||
Base64.getEncoder()
|
||||
.encodeToString(publicKey.encoded)
|
||||
.toByteArray()
|
||||
).joinToString("") { "%02x".format(it) }, publicHash)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,29 +3,44 @@ package io.github.wulkanowy.signer.hebe
|
|||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import java.text.SimpleDateFormat
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.*
|
||||
|
||||
class SignerTest {
|
||||
|
||||
private val fullUrl = "/powiatwulkanowy/123456/api/mobile/register/hebe";
|
||||
private val fingerprint = "7EBA57E1DDBA1C249D097A9FF1C9CCDD45351A6A";
|
||||
private val privateKey = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDCbF5Tt176EpB4cX5U+PZE0XytjJ9ABDZFaBFDkaexbkuNeuLOaARjQEOlUoBmpZQXxAF8HlYqeTvPiTcnSfQIS6EdqpICuQNdwvy6CHFAe2imkbbB0aHPsGep6zH8ZxHbssazkTCnGy0j2ZtGT2/iy1GEvc/p2bOkCVcR1H1GqFp+/XpfaMwi2SRCwc67K8Fu8TjSDKNvcRT9nuenGoPA1CWoOiOCxhQA6gnB8LULPel6TVDxeBVdYor/z2GxFe/m0pa7XAKzveuUDhH8k8NlNG65MjvZhgy9iFs+eBUq7lCZ0nuIsDzjnUrLSl4ciYKj9d94qrUyF8L8D9Rl+0WlAgMBAAECggEAQ6jg3rNmyxIg0rl0ZG/LjEF26RKR7P5KQLcpouESga3HfzHvsjMCq+OWZvciFhazRd4BQkdwZxGPnfa7ieGzmhtvs1pDu8zU/hE4UClV+EG6NpVpC2Q/sn5KZRijaZoY3eMGQUFatBzCBcLZxYspfbyR3ucLbu9DE+foNB1Fh4u9RCDj3bClTsqPcNBIaLMpYr3f/bM1fFbS9LrJ7AXZQtGg/2MH58WsvV67SiYAQqGCzld/Jp74gmod4Ii0w2XWZ7OeixdF2xr1j7TK0dUUlrrOrb1cgOWSOEXyy3RX/iF7R8uuLXiRfo1URh6VNPoOtrC6fHCrCp1iRBo08qOk4QKBgQDxqLrWA7gKcTr2yQeGfETXOAYi0xqbNj5A9eVC0XngxnFuwWc5zyg3Ps3c0UK2qTSSFv4SoeEHQM+U0+9LjYzIRSUH7zy4zBrBlLtTQCysSuuZ9QfgO55b3/QEYkyx6Hz/z/gg53jKHjsUKIftGMwJ6C1M2svbBNYCsWrUuYcsbQKBgQDN9gkVDABIeWUtKDHyB5HGcVbsg7Ji7GhMjdFA0GB+9kR0doKNctrzxKn65BI2uTWg+mxaw5V+UeJOIaeFsv2uClYJYn1F55VT7NIx3CLFv6zFRSiMSKz2W+NkwGjQqR7D3DeEyalpjeQeMdpHZg27LMbdVkzy/cK8EM9ZQlRLGQKBgQCpB2wn5dIE+85Sb6pj1ugP4Y/pK9+gUQCaT2RcqEingCY3Ye/h75QhkDxOB9CyEwhCZvKv9aqAeES5xMPMBOZD7plIQ34lhB3y6SVdxbV5ja3dshYgMZNCkBMOPfOHPSaxh7X2zfEe7qZEI1Vv8bhF9bA54ZBVUbyfhZlD0cFKwQKBgQC9BnXHb0BDQ8br7twH+ZJ8wkC4yRXLXJVMzUujZJtrarHhAXNIRoVU/MXUkcV1m/3wRGV119M4IAbHFnQdbO0N8kaMTmwS4DxYzh0LzbHMM+JpGtPgDENRx3unWD/aYZzuvQnnQP3O9n7Kh46BwNQRWUMamL3+tY8n83WZwhqC4QKBgBTUzHy0sEEZ3hYgwU2ygbzC0vPladw2KqtKy+0LdHtx5pqE4/pvhVMpRRTNBDiAvb5lZmMB/B3CzoiMQOwczuus8Xsx7bEci28DzQ+g2zt0/bC2Xl+992Ass5PP5NtOrP/9QiTNgoFSCrVnZnNzQqpjCrFsjfOD2fiuFLCD6zi6";
|
||||
private val body = "{}";
|
||||
private val fullUrl = "/powiatwulkanowy/123456/api/mobile/register/hebe?lastSyncDate=null"
|
||||
private val keyId = "7eba57e1ddba1c249d097a9ff1c9ccdd45351a6a"
|
||||
private val privatePem = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDCbF5Tt176EpB4cX5U+PZE0XytjJ9ABDZFaBFDkaexbkuNeuLOaARjQEOlUoBmpZQXxAF8HlYqeTvPiTcnSfQIS6EdqpICuQNdwvy6CHFAe2imkbbB0aHPsGep6zH8ZxHbssazkTCnGy0j2ZtGT2/iy1GEvc/p2bOkCVcR1H1GqFp+/XpfaMwi2SRCwc67K8Fu8TjSDKNvcRT9nuenGoPA1CWoOiOCxhQA6gnB8LULPel6TVDxeBVdYor/z2GxFe/m0pa7XAKzveuUDhH8k8NlNG65MjvZhgy9iFs+eBUq7lCZ0nuIsDzjnUrLSl4ciYKj9d94qrUyF8L8D9Rl+0WlAgMBAAECggEAQ6jg3rNmyxIg0rl0ZG/LjEF26RKR7P5KQLcpouESga3HfzHvsjMCq+OWZvciFhazRd4BQkdwZxGPnfa7ieGzmhtvs1pDu8zU/hE4UClV+EG6NpVpC2Q/sn5KZRijaZoY3eMGQUFatBzCBcLZxYspfbyR3ucLbu9DE+foNB1Fh4u9RCDj3bClTsqPcNBIaLMpYr3f/bM1fFbS9LrJ7AXZQtGg/2MH58WsvV67SiYAQqGCzld/Jp74gmod4Ii0w2XWZ7OeixdF2xr1j7TK0dUUlrrOrb1cgOWSOEXyy3RX/iF7R8uuLXiRfo1URh6VNPoOtrC6fHCrCp1iRBo08qOk4QKBgQDxqLrWA7gKcTr2yQeGfETXOAYi0xqbNj5A9eVC0XngxnFuwWc5zyg3Ps3c0UK2qTSSFv4SoeEHQM+U0+9LjYzIRSUH7zy4zBrBlLtTQCysSuuZ9QfgO55b3/QEYkyx6Hz/z/gg53jKHjsUKIftGMwJ6C1M2svbBNYCsWrUuYcsbQKBgQDN9gkVDABIeWUtKDHyB5HGcVbsg7Ji7GhMjdFA0GB+9kR0doKNctrzxKn65BI2uTWg+mxaw5V+UeJOIaeFsv2uClYJYn1F55VT7NIx3CLFv6zFRSiMSKz2W+NkwGjQqR7D3DeEyalpjeQeMdpHZg27LMbdVkzy/cK8EM9ZQlRLGQKBgQCpB2wn5dIE+85Sb6pj1ugP4Y/pK9+gUQCaT2RcqEingCY3Ye/h75QhkDxOB9CyEwhCZvKv9aqAeES5xMPMBOZD7plIQ34lhB3y6SVdxbV5ja3dshYgMZNCkBMOPfOHPSaxh7X2zfEe7qZEI1Vv8bhF9bA54ZBVUbyfhZlD0cFKwQKBgQC9BnXHb0BDQ8br7twH+ZJ8wkC4yRXLXJVMzUujZJtrarHhAXNIRoVU/MXUkcV1m/3wRGV119M4IAbHFnQdbO0N8kaMTmwS4DxYzh0LzbHMM+JpGtPgDENRx3unWD/aYZzuvQnnQP3O9n7Kh46BwNQRWUMamL3+tY8n83WZwhqC4QKBgBTUzHy0sEEZ3hYgwU2ygbzC0vPladw2KqtKy+0LdHtx5pqE4/pvhVMpRRTNBDiAvb5lZmMB/B3CzoiMQOwczuus8Xsx7bEci28DzQ+g2zt0/bC2Xl+992Ass5PP5NtOrP/9QiTNgoFSCrVnZnNzQqpjCrFsjfOD2fiuFLCD6zi6"
|
||||
private val body = "{}"
|
||||
|
||||
@Test
|
||||
fun `values should match`() {
|
||||
val (digest, canonicalUrl, signature) = getSignatureValues(fingerprint, privateKey, body, fullUrl, getDate("2020-04-14 04:14:16"))
|
||||
|
||||
assertEquals("SHA-256=RBNvo1WzZ4oRRq0W9+hknpT7T8If536DEMBg9hyq/4o=", digest);
|
||||
assertEquals("api%2fmobile%2fregister%2fhebe", canonicalUrl);
|
||||
assertEquals("keyId=\"7EBA57E1DDBA1C249D097A9FF1C9CCDD45351A6A\"," +
|
||||
val headers = getSignatureHeaders(keyId, privatePem, body, fullUrl, ZonedDateTime.parse("2021-02-18T16:36:53Z"))
|
||||
val signature = "keyId=\"7eba57e1ddba1c249d097a9ff1c9ccdd45351a6a," +
|
||||
"headers=\"vCanonicalUrl Digest vDate\"," +
|
||||
"algorithm=\"sha256withrsa\"," +
|
||||
"signature=Base64(SHA256withRSA(mIVNkthTzTHmmXG1qxv1Jpt3uRlyhbj7VHysbCNpl0zXCCzuwTXsuCrfjexDDXsyJVo/LznQKOyvOaW4tEfrBobxtbtTnp7zYi54bdvAZa3pvM02yvkH4i/DvTLDKRO0R9UDZ1LraGrOTsIe3m3mQ21NOynVqCKadeqod8Y7l4YUlVYEmrtq/7xbCwr0qdne6G67eY4Amj6ffbG3TkVLpUrEETBnAC7oFjGYKhcRyvltAi+lcv6omANz1gwELf+Vmsa8NwFo/YGwY3R23z15athU/1iC1JcrECBLC8nRM1+KlvyIqx2HX6RG5R1cMOwBWVg6pRKUdrhxYbQ+VQ8Cag==))",
|
||||
signature);
|
||||
"algorithm=\"sha256\"," +
|
||||
"signature=Base64(sha256withrsa(JLdhYWv05+f7KTstWsCgt0rU2QQpA+jZM6VaVKFYe0Q4hrKKVq5/ZPB3ttBJ0RwD+MGX2mePkYm3BiLdCOqoZfAyylpHCnnQ4lbFOX45sMogQQoFbhmIQF1ZwVxRKrn/lbrd+VsLsInXWn74CMNIOz55p/WrwV3J+w5g1FpYSRM/LVzQMXvcRRXx6WSmfo/qd1H6TH33EVU+fPTw3lhGpAPDl+clZyUDyfWrmxaRvJ/ag2LNdtGVNXDQfyMO7diOJQtqUnWsJoMh5iNFApd9nHgB1Fkmb62aDwGDGtDz65IA7ArdjNW56IXeogQyp+Vv/icap3/ujdQl5zjXssL4Sg==))"
|
||||
|
||||
assertEquals("SHA-256=RBNvo1WzZ4oRRq0W9+hknpT7T8If536DEMBg9hyq/4o=", headers["Digest"])
|
||||
assertEquals("api%2fmobile%2fregister%2fhebe%3flastsyncdate%3dnull", headers["vCanonicalUrl"])
|
||||
assertEquals("Thu, 18 Feb 2021 16:36:53 GMT", headers["vDate"])
|
||||
assertEquals(signature, headers["Signature"])
|
||||
}
|
||||
|
||||
private fun getDate(date: String) = SimpleDateFormat("yyyy-MM-dd hh:mm:ss").apply {
|
||||
timeZone = TimeZone.getTimeZone("UTC")
|
||||
}.parse(date)
|
||||
@Test
|
||||
fun `values should match when no body`() {
|
||||
val headers = getSignatureHeaders(keyId, privatePem, null, fullUrl, ZonedDateTime.parse("2021-02-18T16:36:53Z"))
|
||||
val signature = "keyId=\"7eba57e1ddba1c249d097a9ff1c9ccdd45351a6a," +
|
||||
"headers=\"vCanonicalUrl vDate\"," +
|
||||
"algorithm=\"sha256\"," +
|
||||
"signature=Base64(sha256withrsa(HF8AbXOjH8CBu+xfviwSXz46GwuRDQL47Bb6rIumdEZ0l6sUF7oMakhFVPBc3G43sNSSIZsxTgdBu0GB+66OHj1ce6/E5WTUi01/chs22mqTFfGbkqQ2eebmmSciW1qGiYy/0IfwidTVJXJa4EYWjsM3538WMvaeoOuqAKOwrOGgQG0qmuR8ZUKIit3R0BbQTTBlV5jX2MVTmTTo2Knx5q9Hz5PTPdyzuKYGD5mXRhx42WK6EzSzxzbdY8+s3esCCEEAeXQS+Bps0nF+h8xFXwc+QHgPjlGVX2HE/EEw+Kp6AW3ub8QjvbvO+mkNU/JaFQBOvRvCy0heMjOTt66QxA==))"
|
||||
|
||||
assertEquals(null, headers["Digest"])
|
||||
assertEquals("api%2fmobile%2fregister%2fhebe%3flastsyncdate%3dnull", headers["vCanonicalUrl"])
|
||||
assertEquals("Thu, 18 Feb 2021 16:36:53 GMT", headers["vDate"])
|
||||
assertEquals(signature, headers["Signature"])
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue