EllipticCurveKeyPair
EllipticCurveKeyPair copied to clipboard
Signature validates locally but not on remote server
Hi, I wanto to use your library for my project which uses JSON Web Token JWT = base64(header) + "." + base64(payload) + "." + base64(signature)
In the documentation of my project I have an example
eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJ0ZXN0dXNlcm5hbWUiLCJzdWIiOiJ0ZXN0Y2xpZW50aWQiLCJpYXQiOjE1MDE1MDk3ODIsImV4cCI6MTUwMTUwOTg0Mn0.SGNpyl3zRA_ptRhA0lFH0o7-nhf3mpxE95ss37_jHYbCnwlRb4zDvVaYCj9DlpppU4U0y3vIPEqM44vV2UZ5Iw
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7u8bg5nOOsxZvkdmK+Zcvx+byi93
iQ+lMWHsAcOaOAwbmcSU3lKEXKu3gp/ymiXUhIyFuw2Pkxfe7T1e4HSmqA==
-----END PUBLIC KEY-----
And when I paste that token here https://jwt.io/ with the public key - it validates. However when I use your library to sign the header and payload I get info about invalid signature (on JWT page)
let signatureInput = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJ0ZXN0dXNlcm5hbWUiLCJzdWIiOiJ0ZXN0Y2xpZW50aWQiLCJpYXQiOjE1MDE1MDk3ODIsImV4cCI6MTUwMTUwOTg0Mn0"
let signature = createSignature(signatureInput)
let token = signatureInput + "." + signature
func createSignature(_ input: String) -> String {
guard
let data = input.data(using: .utf8),
let signature = try? keysManager.sign(data, hash: .sha256)
else {
fatalError("Cannot create signature")
}
return signature.base64EncodedString(options: .init(rawValue: 0))
}
The verification passes your method
do {
try keysManager.verify(signature: Data(base64Encoded: signature)!,
originalDigest: signatureInput.data(using: .utf8)!,
hash: .sha256)
print("valid")
}
catch {
print("error \(error)")
}
Unbased content of header and payload are
header // eyJhbGciOiJFUzI1NiJ9
{
"alg": "ES256"
}
payload // eyJpc3MiOiJ0ZXN0dXNlcm5hbWUiLCJzdWIiOiJ0ZXN0Y2xpZW50aWQiLCJpYXQiOjE1MDE1MDk3ODIsImV4cCI6MTUwMTUwOTg0Mn0
{
"iss": "testusername",
"sub": "testclientid",
"iat": 1501509782,
"exp": 1501509842
}
Am I doing something wrong with signing?
Hi! Thanks for letting me know about this use case!
It was slightly tricky. 2 things where off.
- The signature that Security/EllipticCurveKeyPair produces is in DER format. The API expects a raw signature of 64 bytes for ES256.
- The API expects a base64 uri safe variant
I managed to produce a valid JWToken
eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJ0ZXN0dXNlcm5hbWUiLCJzdWIiOiJ0ZXN0Y2xpZW50aWQiLCJpYXQiOjE1MDE1MDk3ODIsImV4cCI6MTUwMTUwOTg0Mn0.9bUeggjl9sOdfxqOOcMv7uE6MUBrhEMWEFDLI1xiD4HwO8oSK_TuveST4imEug13h7hthgy1uwYPa0Fb7M4N3w
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEXum2ZYo0Qp2foJRIKPP2eGNT82y6
GgZgRGKWB8DArDhKQAhjp/RQFCoP8Olq4QtL5l4jcdKZhvOTAd47r7tAvQ==
-----END PUBLIC KEY-----
Here's the code
import EllipticCurveKeyPair
extension Data {
func base64EncodedStringURISafe() -> String {
return self.base64EncodedString()
.replacingOccurrences(of: "+", with: "-")
.replacingOccurrences(of: "/", with: "_")
.replacingOccurrences(of: "=", with: "")
}
}
func DEREC256SignatureDecode(_ signature: Data) -> Data {
// example https://lapo.it/asn1js/#3046022100F309E36DFA5FE0BFBF5C3855E06E9C3C7D04DE347E2B345C3392DDB98E13BE6302210080372B3BBAE5E370B976092B8AA64F4EF1025FFE893D0046FA085F256AE04761
var decoded = signature
let maxChunkSize = 32
decoded.removeFirst() // removing sequence header
decoded.removeFirst() // removing sequence size
decoded.removeFirst() // removing 'r' element header
let rLength = Int(decoded.removeFirst()) // removing 'r' element length
let r = decoded.prefix(rLength).suffix(maxChunkSize) // read out 'r' bytes and discard any padding
decoded.removeFirst(Int(rLength)) // removing 'r' bytes
decoded.removeFirst() // 's' element header
let sLength = Int(decoded.removeFirst()) // 's' element length
let s = decoded.prefix(sLength).suffix(maxChunkSize) // read out 's' bytes and discard any padding
return Data(r) + Data(s)
}
let keysManager: EllipticCurveKeyPair.Manager = {
let publicAccessControl = EllipticCurveKeyPair.AccessControl(protection: kSecAttrAccessibleAlwaysThisDeviceOnly, flags: [])
let privateAccessControl = EllipticCurveKeyPair.AccessControl(protection: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, flags: [.privateKeyUsage, .userPresence])
let config = EllipticCurveKeyPair.Config(
publicLabel: "test.sign.public",
privateLabel: "test.sign.private",
operationPrompt: "Json web token",
publicKeyAccessControl: publicAccessControl,
privateKeyAccessControl: privateAccessControl,
token: .secureEnclave)
return EllipticCurveKeyPair.Manager(config: config)
}()
func testJWT() {
do {
try? keysManager.deleteKeyPair()
let headerAndPayload = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJ0ZXN0dXNlcm5hbWUiLCJzdWIiOiJ0ZXN0Y2xpZW50aWQiLCJpYXQiOjE1MDE1MDk3ODIsImV4cCI6MTUwMTUwOTg0Mn0"
let derSignature = try keysManager.sign(headerAndPayload.data(using: .utf8)!, hash: .sha256)
let decodedSignature = DEREC256SignatureDecode(derSignature)
let token = headerAndPayload + "." + decodedSignature.base64EncodedStringURISafe()
print("headerAndPayloadAsString: \(headerAndPayload)")
print("signature: \(derSignature.base64EncodedStringURISafe())")
print("decoded signature: \(decodedSignature.base64EncodedStringURISafe())")
print("token: \(token)")
print("public key: \n\(try keysManager.publicKey().data().PEM)")
try keysManager.verify(signature: derSignature,
originalDigest: headerAndPayload.data(using: .utf8)!,
hash: .sha256)
print("valid!")
} catch {
print("error: \(error)")
}
}
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
testJWT()
}
There's some mutual interests here https://github.com/yourkarma/JWT/issues/116
Hi, everything works! I'm really grateful for the speed of response and example!
Great! I'm keeping this open until I have this included somehow
That would be nice too. The sign method could have a param like so
enum SignatureFormat {
case der
case raw
}
public func sign(_ digest: Data, hash: Hash, format: SignatureFormat = .der, context: LAContext? = nil) throws -> Data {
}
but this is just a suggestion
👍👍 Yep, either that or return an object with two variables.