:heavy_exclamation_mark: Important! Before you proceed, please read the EUDI Wallet Reference Implementation project description
This is a library offering a DSL (domain-specific language) for defining how a set of claims should be made selectively disclosable.
Library implements SD-JWT draft8 is implemented in Swift.
To issue a SD-JWT, an Issuer
should have:
In the example bellow, Issuer decides to issue an SD-JWT as follows:
sub
,iss
, iat
, exp
)address
using structured disclosure. This allows to individually
disclose every subclaim of address
let issuersKeyPair: KeyPair!
let signedSDJWT = try SDJWTIssuer.issue(issuersPrivateKey: issuersKeyPair.private,
decoys: 0, // Can be omitted
header: .init(algorithm: .ES256)) {
ConstantClaims.sub(subject: "6c5c0a49-b589-431d-bae7-219122a9ec2c")
ConstantClaims.iss(domain: "https://example.com/issuer")
ConstantClaims.iat(time: 1516239022)
ConstantClaims.exp(time: 1516239022)
ObjectClaim("address") {
FlatDisclosedClaim("street_address", "Schulstr. 12")
FlatDisclosedClaim("locality", "Schulpforta")
FlatDisclosedClaim("region", "Sachsen-Anhalt")
FlatDisclosedClaim("country", "DE")
}
}
In this case, the SD-JWT is expected to be in serialized form.
Holder must know:
the public key of the Issuer and the algorithm used by the Issuer to sign the SD-JWT
let unverifiedSdJwtString = "..."
let issuersKeyPair: KeyPair!
SDJWTVerifier(parser: CompactParser(serialisedString: unverifiedSdJwtString))
.verifyIssuance { jws in
SignatureVerifier(signedJWT: jws, publicKey: issuersKeyPair.public)
}
In simple (not enveloped) format
In this case, the SD-JWT is expected to be in Combined Presentation format. Verifier should know the public key of the Issuer and the algorithm used by the Issuer to sign the SD-JWT. Also, if verification includes Key Binding, the Verifier must also know a how the public key of the Holder was included in the SD-JWT and which algorithm the Holder used to sign the Key Binding JWT
// Issue a SDJWT to passed to a holder from an issuer
// Including Holders Public key
let issuerSignedSDJWT = try SDJWTIssuer
.issue(issuersPrivateKey: issuersKeyPair.private,
header: .init(algorithm: .ES256)) {
// Claims disclosed or plain
...
// add holders public key in the payload
ObjectClaim("cnf") {
ObjectClaim("jwk") {
PlainClaim("kty", "EC")
PlainClaim("x", "EOid5YEjFXpCzaqyEqckcA5TBGxWEVYCiKz05qO5r_c")
PlainClaim("y", "7TTgK6fW5oxaN8m22f_HPVJ9Ny3KBKIvLcBIpUpk-7A")
PlainClaim("crv", "P-256")
}
}
}
// Issue a SDJWT for presentation to a verifier
// Expect a verifier Challenge in json format to sign
// And prove identity
// Chose the subset of disclosures to present if needed
let holderSDJWTRepresentation = try SDJWTIssuer
.presentation(holdersPrivateKey: holdersKeyPair.private,
signedSDJWT: issuerSignedSDJWT,
disclosuresToPresent: issuerSignedSDJWT.disclosures.filter({ _ in true }),
keyBindingJWT: KBJWT(header: .init(algorithm: .ES256),
kbJwtPayload: VerifiersChallenge.json)
SDJWTVerifier(sdJwt: holderSDJWTRepresentation)
.verifyPresentation { jws in
try SignatureVerifier(signedJWT: jws, publicKey: issuersKeyPair.public)
} keyBindingVerifier: { jws, holdersPublicKey in
try KeyBindingVerifier(challenge: jws, extractedKey: holdersPublicKey)
}
In enveloped format
In this case, the SD-JWT is expected to be in envelope format. Verifier should know:
let sdjwtOnPayload = "...."
try SDJWTVerifier(parser: CompactParser(serialisedString: sdjwtOnPayload))
.verifyEnvelope(envelope: envelopedJws) { jws in
// to verify the enveloped sdjwt
try SignatureVerifier(signedJWT: jws, publicKey: issuersKeyPair.public)
} holdersSignatureVerifier: {
// to verify the container jwt
try SignatureVerifier(signedJWT: envelopedJws, publicKey: holdersKeyPair.public)
} claimVerifier: { audClaim, iat in
ClaimsVerifier(iat: iat,
iatValidWindow: .init(startTime: Date(),
endTime: Date()),
audClaim: audClaim,
expectedAud: "clientId")
}
Given a complex structure as per Example 3: Complex Structured SD-JWT and a subset of claims we can recreate the initial JSON of the SD-JWT.
["2GLC42sKQveCfGfryNRN9w", "time", "2012-04-23T18:25Z"]
["Pc33JM2LchcU_lHggv_ufQ", {"_sd": ["9wpjVPWuD7PK0nsQDL8B06lmdgV3LVybhHydQpTNyLI", "G5EnhOAOoU9X_6QMNvzFXjpEA_Rc-AEtm1bG_wcaKIk", "IhwFrWUB63RcZq9yvgZ0XPc7Gowh3O2kqXeBIswg1B4", "WpxQ4HSoEtcTmCCKOeDslB_emucYLz2oO8oHNr1bEVQ"]}]
["eI8ZWm9QnKPpNPeNenHdhQ", "method", "pipp"]
["G02NSrQfjFXQ7Io09syajA", "given_name", "Max"]
["lklxF5jMYlGTPUovMNIvCA", "family_name", "M\u00fcller"]
["y1sVU5wdfJahVdgwPgS7RQ", "address", {"locality": "Maxstadt", "postal_code": "12344", "country": "DE", "street_address": "Weidenstra\u00dfe 22"}]
let example3SDJWTSerialisedFormat = "..."
let sdjwt = CompactParser(serialisedString: example3SDJWTSerialisedFormat).getSignedSdJwt()
// array of digests of disclosures found on payload for collision
sdjwt.recreateClaims().digestsFoundOnPayload checking
// the recreated JSON
sdjwt.recreateClaims().recreatedClaims
The recreated JSON output
{
"iat" : 1683000000,
"verified_claims" : {
"claims" : {
"given_name" : "Max",
"family_name" : "Müller",
"address" : {
"country" : "DE",
"locality" : "Maxstadt",
"street_address" : "Weidenstraße 22",
"postal_code" : "12344"
}
},
"verification" : {
"trust_framework" : "de_aml",
"time" : "2012-04-23T18:25Z",
"evidence" : [
{
"method" : "pipp"
}
]
}
},
"iss" : "https://example.com/issuer",
"exp" : 1883000000
}
All examples assume that we have the following claim set
{
"sub": "6c5c0a49-b589-431d-bae7-219122a9ec2c",
"address": {
"street_address": "Schulstr. 12",
"locality": "Schulpforta",
"region": "Sachsen-Anhalt",
"country": "DE"
}
}
We welcome contributions to this project. To ensure that the process is smooth for everyone involved, follow the guidelines found in CONTRIBUTING.md.
Copyright (c) 2023 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.