Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Signature validates locally but not on remote server #31

Open
smamczak opened this issue Jul 30, 2018 · 6 comments
Open

Signature validates locally but not on remote server #31

smamczak opened this issue Jul 30, 2018 · 6 comments

Comments

@smamczak
Copy link

smamczak commented Jul 30, 2018

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?

@hfossli
Copy link
Contributor

hfossli commented Jul 31, 2018

Hi! Thanks for letting me know about this use case!

It was slightly tricky. 2 things where off.

  1. The signature that Security/EllipticCurveKeyPair produces is in DER format. The API expects a raw signature of 64 bytes for ES256.
  2. 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()
    }

@hfossli
Copy link
Contributor

hfossli commented Jul 31, 2018

There's some mutual interests here yourkarma/JWT#116

@smamczak
Copy link
Author

Hi, everything works! I'm really grateful for the speed of response and example!

@hfossli
Copy link
Contributor

hfossli commented Jul 31, 2018

Great! I'm keeping this open until I have this included somehow

@smamczak
Copy link
Author

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

@hfossli
Copy link
Contributor

hfossli commented Jul 31, 2018

👍👍 Yep, either that or return an object with two variables.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants