-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCryptoGenerator.kt
More file actions
163 lines (152 loc) · 6.31 KB
/
CryptoGenerator.kt
File metadata and controls
163 lines (152 loc) · 6.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
/*
* Copyright (c) 2023-2026 European Commission
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package eu.europa.ec.eudi.openid4vci
import com.nimbusds.jose.JOSEObjectType
import com.nimbusds.jose.JWSAlgorithm
import com.nimbusds.jose.JWSHeader
import com.nimbusds.jose.JWSSigner
import com.nimbusds.jose.crypto.ECDSASigner
import com.nimbusds.jose.jwk.Curve
import com.nimbusds.jose.jwk.ECKey
import com.nimbusds.jose.jwk.JWK
import com.nimbusds.jose.jwk.KeyUse
import com.nimbusds.jose.jwk.gen.ECKeyGenerator
import com.nimbusds.jwt.JWTClaimsSet
import com.nimbusds.jwt.SignedJWT
import eu.europa.ec.eudi.openid4vci.internal.fromNimbusEcKey
import eu.europa.ec.eudi.openid4vci.internal.fromNimbusEcKeys
import java.security.KeyFactory
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.security.interfaces.ECPrivateKey
import java.security.spec.PKCS8EncodedKeySpec
import java.time.Instant.now
import java.util.*
object CryptoGenerator {
fun randomECSigningKey(curve: Curve): ECKey = ECKeyGenerator(curve)
.keyUse(KeyUse.SIGNATURE)
.keyID(UUID.randomUUID().toString())
.issueTime(Date(System.currentTimeMillis()))
.generate()
fun ecSigner(curve: Curve = Curve.P_256, alg: JWSAlgorithm = JWSAlgorithm.ES256): Signer<JWK> {
require(alg in JWSAlgorithm.Family.EC)
val keyPair = randomECSigningKey(curve)
return Signer.fromNimbusEcKey(
keyPair,
keyPair.toPublicJWK(),
secureRandom = null,
provider = null,
)
}
fun noKeyAttestationJwtProofsSpec(
curve: Curve = Curve.P_256,
keysNo: Int = 1,
): ProofsSpecification {
val ecKeys = List(keysNo) { randomECSigningKey(curve) }
val batchSigner = BatchSigner.fromNimbusEcKeys(
ecKeyPairs = ecKeys.associateWith { JwtBindingKey.Jwk(it.toPublicJWK()) },
secureRandom = null,
provider = null,
)
return ProofsSpecification.JwtProofs.NoKeyAttestation(batchSigner)
}
fun keyAttestationJwtProofsSpec(
curve: Curve = Curve.P_256,
attestedKeysCount: Int = 3,
): ProofsSpecification {
val ecKeys = List(attestedKeysCount) { randomECSigningKey(curve) }
val signerProvider: suspend (Nonce?) -> Signer<KeyAttestationJWT> = { cNonce ->
Signer.fromNimbusEcKey(
ecPrivateKey = ecKeys[0],
keyInfo =
keyAttestationJwt(
attestedKeys = ecKeys.map { it.toPublicJWK() },
cNonce,
),
secureRandom = null,
provider = null,
)
}
return ProofsSpecification.JwtProofs.WithKeyAttestation(signerProvider, 1)
}
fun attestationProofSpec(
curve: Curve = Curve.P_256,
keysNo: Int = 3,
) =
ProofsSpecification.AttestationProof { nonce ->
keyAttestationJwt(
List(keysNo) {
randomECSigningKey(curve)
},
nonce,
)
}
// Helper to load an EC private key from PEM file
private fun loadECPrivateKeyFromFile(resourcePath: String): ECPrivateKey {
val pem = CryptoGenerator::class.java.classLoader.getResource(resourcePath)?.readText()
?: error("Private key file not found: $resourcePath")
val base64 = pem
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replace("\r", "")
.replace("\n", "")
.trim()
val keyBytes = Base64.getDecoder().decode(base64)
val keySpec = PKCS8EncodedKeySpec(keyBytes)
val kf = KeyFactory.getInstance("EC")
return kf.generatePrivate(keySpec) as ECPrivateKey
}
// Helper to load X.509 certificate from PEM file
private fun loadCertificateFromFile(resourcePath: String): X509Certificate {
val certStream = CryptoGenerator::class.java.classLoader.getResourceAsStream(resourcePath)
?: error("Certificate file not found: $resourcePath")
val cf = CertificateFactory.getInstance("X.509")
return cf.generateCertificate(certStream) as X509Certificate
}
fun keyAttestationJwt(
attestedKeys: List<JWK>? = null,
nonce: Nonce? = null,
) = run {
val privateKey = loadECPrivateKeyFromFile("eu/europa/ec/eudi/openid4vci/internal/key_attestation_jwt.key")
val certificate = loadCertificateFromFile("eu/europa/ec/eudi/openid4vci/internal/key_attestation_jwt.cert")
val jwt = keyAttestationJwt(
attestedKeys = attestedKeys ?: List(3) { randomECSigningKey(Curve.P_256) },
certificate = certificate,
signer = ECDSASigner(privateKey),
nonce,
)
KeyAttestationJWT(jwt.serialize())
}
private fun keyAttestationJwt(
attestedKeys: List<JWK>,
certificate: X509Certificate,
signer: JWSSigner,
nonce: Nonce? = null,
): SignedJWT = SignedJWT(
JWSHeader.Builder(JWSAlgorithm.ES256)
.type(JOSEObjectType(OpenId4VCISpec.KEY_ATTESTATION_JWT_TYPE))
.x509CertChain(listOf(com.nimbusds.jose.util.Base64.encode(certificate.encoded)))
.build(),
JWTClaimsSet.Builder().apply {
claim(OpenId4VCISpec.KEY_ATTESTATION_ATTESTED_KEYS, attestedKeys.map { it.toPublicJWK().toJSONObject() })
claim(OpenId4VCISpec.KEY_ATTESTATION_KEY_STORAGE, listOf("iso_18045_moderate"))
claim(OpenId4VCISpec.KEY_ATTESTATION_USER_AUTHENTICATION, listOf("iso_18045_moderate"))
nonce?.let { claim("nonce", nonce.value) }
issueTime(Date.from(now()))
expirationTime(Date.from(now().plusSeconds(3600)))
}.build(),
).apply { sign(signer) }
}