Created
November 19, 2021 04:44
-
-
Save ryancumley/f59f1672e3b6345c68b1ed129073d46f to your computer and use it in GitHub Desktop.
JWT verification and parsing playground
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import Security | |
//https://blog.kaltoun.cz/verifying-rsa-jwt-signatures-in-swift/ | |
let rawToken: String = "#id_token=eyJraWQiOiJ5eVFmUVVETnFYNklOVGxLY1wvMVlWaUVhcXJKa3k4KzhvczlJejZyUmVoWT0iLCJhbGciOiJSUzI1NiJ9.eyJhdF9oYXNoIjoiX1ZFSjFtbnlQWGxsZ3NjRWJJcmVfdyIsInN1YiI6ImZiOGY1YmFmLWU0MjUtNGQ5MC1hZDE5LTdkZDZiZWE5ZWVjMSIsImF1ZCI6IjR2ZmtmNWQ0ZTlvOWV0ZjhhOXN2bHVtbjBnIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsInRva2VuX3VzZSI6ImlkIiwiYXV0aF90aW1lIjoxNjE2NjQ1MzA0LCJpc3MiOiJodHRwczpcL1wvY29nbml0by1pZHAudXMtZWFzdC0yLmFtYXpvbmF3cy5jb21cL3VzLWVhc3QtMl92a2tTbmxraWwiLCJjb2duaXRvOnVzZXJuYW1lIjoidGVzdDEiLCJleHAiOjE2MTY2NDg5MDQsImlhdCI6MTYxNjY0NTMwNCwiZW1haWwiOiJyeWFuY3VtbGV5K3Rlc3QxcmVudGFkb2xwaGluQGdtYWlsLmNvbSJ9.LAnce4dLrRWc2V5e4YnNUMtaY96IS71zaKz7N48Lb1AO06P2_xeDFFuj18JPfWo9thROvzdnlWS21HyTMHZVJF6m-wy0nkXcc97-VjEIQSNU-UaJjWSZI86WcFku0HVr9_13B2C12K-eDGDhacUQN8or9dyKNNdNdOHAjHWJy4i1GYYzyJRopBQgwBUwpgNLfNOe9HWYSHbVG58-1__WsIndcCKr_Ix5FVjJ7hedBEMBIGZkeFhPVHHEE6MM0enWD3r9bYZ1g-a8CcQ5XAt7IfOzLT25rSaFk1fPUOanMbc2-goulyoq-DzLp7MKmlozo-RcmgnlW5TbOGXOis-LXg&access_token=eyJraWQiOiJ1KzRTbWd0UnFsdWZTU1BKUFYzRWJ6QnhaNlEyMnhwMlVSZE0wNURzQk1vPSIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJmYjhmNWJhZi1lNDI1LTRkOTAtYWQxOS03ZGQ2YmVhOWVlYzEiLCJ0b2tlbl91c2UiOiJhY2Nlc3MiLCJzY29wZSI6ImF3cy5jb2duaXRvLnNpZ25pbi51c2VyLmFkbWluIHBob25lIG9wZW5pZCBwcm9maWxlIGVtYWlsIiwiYXV0aF90aW1lIjoxNjE2NjQ1MzA0LCJpc3MiOiJodHRwczpcL1wvY29nbml0by1pZHAudXMtZWFzdC0yLmFtYXpvbmF3cy5jb21cL3VzLWVhc3QtMl92a2tTbmxraWwiLCJleHAiOjE2MTY2NDg5MDQsImlhdCI6MTYxNjY0NTMwNCwidmVyc2lvbiI6MiwianRpIjoiMDE5MzJmYzYtODEwMC00ZDA3LTkyYzAtZjc3Y2ZmNzBhNjRlIiwiY2xpZW50X2lkIjoiNHZma2Y1ZDRlOW85ZXRmOGE5c3ZsdW1uMGciLCJ1c2VybmFtZSI6InRlc3QxIn0.dpW4iBCefhtm5Un6oMB8RbzTvQxJyY87Gqxp3kfLaheGfSBA6cWRtHPFDnbFgtk9Vo1TmHKppqvHSwDg43wfP4qbTocIXs2j8RuLcP92KRa_4B1rP2pb-L26LKi1-l53XQJ_-63pHLLcrM5CloqST4SPXDm30e8mw_vswNZ4i9bibzokOzusPKRKpq0HbujeilUC1Jojuu2FISge0jtrmoBQ9nkXXCiEEFmykL1ZJ1hpbzmxA90gYfCxDnS6KOPVq_BwerLeV_TghUNYn62TAu4x33ttpMjOI_fWHUfKn_wojOC1_S8EU2u6cAxOrtir5o7nrP1fq-vXynlAb3SrHQ&expires_in=3600&token_type=Bearer" | |
let idToken = "eyJraWQiOiJ5eVFmUVVETnFYNklOVGxLY1wvMVlWaUVhcXJKa3k4KzhvczlJejZyUmVoWT0iLCJhbGciOiJSUzI1NiJ9.eyJhdF9oYXNoIjoiX1ZFSjFtbnlQWGxsZ3NjRWJJcmVfdyIsInN1YiI6ImZiOGY1YmFmLWU0MjUtNGQ5MC1hZDE5LTdkZDZiZWE5ZWVjMSIsImF1ZCI6IjR2ZmtmNWQ0ZTlvOWV0ZjhhOXN2bHVtbjBnIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsInRva2VuX3VzZSI6ImlkIiwiYXV0aF90aW1lIjoxNjE2NjQ1MzA0LCJpc3MiOiJodHRwczpcL1wvY29nbml0by1pZHAudXMtZWFzdC0yLmFtYXpvbmF3cy5jb21cL3VzLWVhc3QtMl92a2tTbmxraWwiLCJjb2duaXRvOnVzZXJuYW1lIjoidGVzdDEiLCJleHAiOjE2MTY2NDg5MDQsImlhdCI6MTYxNjY0NTMwNCwiZW1haWwiOiJyeWFuY3VtbGV5K3Rlc3QxcmVudGFkb2xwaGluQGdtYWlsLmNvbSJ9.LAnce4dLrRWc2V5e4YnNUMtaY96IS71zaKz7N48Lb1AO06P2_xeDFFuj18JPfWo9thROvzdnlWS21HyTMHZVJF6m-wy0nkXcc97-VjEIQSNU-UaJjWSZI86WcFku0HVr9_13B2C12K-eDGDhacUQN8or9dyKNNdNdOHAjHWJy4i1GYYzyJRopBQgwBUwpgNLfNOe9HWYSHbVG58-1__WsIndcCKr_Ix5FVjJ7hedBEMBIGZkeFhPVHHEE6MM0enWD3r9bYZ1g-a8CcQ5XAt7IfOzLT25rSaFk1fPUOanMbc2-goulyoq-DzLp7MKmlozo-RcmgnlW5TbOGXOis-LXg" | |
let accessToken = "eyJraWQiOiJ1KzRTbWd0UnFsdWZTU1BKUFYzRWJ6QnhaNlEyMnhwMlVSZE0wNURzQk1vPSIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJmYjhmNWJhZi1lNDI1LTRkOTAtYWQxOS03ZGQ2YmVhOWVlYzEiLCJ0b2tlbl91c2UiOiJhY2Nlc3MiLCJzY29wZSI6ImF3cy5jb2duaXRvLnNpZ25pbi51c2VyLmFkbWluIHBob25lIG9wZW5pZCBwcm9maWxlIGVtYWlsIiwiYXV0aF90aW1lIjoxNjE2NjQ1MzA0LCJpc3MiOiJodHRwczpcL1wvY29nbml0by1pZHAudXMtZWFzdC0yLmFtYXpvbmF3cy5jb21cL3VzLWVhc3QtMl92a2tTbmxraWwiLCJleHAiOjE2MTY2NDg5MDQsImlhdCI6MTYxNjY0NTMwNCwidmVyc2lvbiI6MiwianRpIjoiMDE5MzJmYzYtODEwMC00ZDA3LTkyYzAtZjc3Y2ZmNzBhNjRlIiwiY2xpZW50X2lkIjoiNHZma2Y1ZDRlOW85ZXRmOGE5c3ZsdW1uMGciLCJ1c2VybmFtZSI6InRlc3QxIn0.dpW4iBCefhtm5Un6oMB8RbzTvQxJyY87Gqxp3kfLaheGfSBA6cWRtHPFDnbFgtk9Vo1TmHKppqvHSwDg43wfP4qbTocIXs2j8RuLcP92KRa_4B1rP2pb-L26LKi1-l53XQJ_-63pHLLcrM5CloqST4SPXDm30e8mw_vswNZ4i9bibzokOzusPKRKpq0HbujeilUC1Jojuu2FISge0jtrmoBQ9nkXXCiEEFmykL1ZJ1hpbzmxA90gYfCxDnS6KOPVq_BwerLeV_TghUNYn62TAu4x33ttpMjOI_fWHUfKn_wojOC1_S8EU2u6cAxOrtir5o7nrP1fq-vXynlAb3SrHQ" | |
///Caseless enum namespace to deal with the JWT's returned by AWS.Cognito | |
enum JWT { | |
struct IDToken: Decodable { | |
let header: TokenHeader | |
let body: IDTokenBody | |
} | |
struct AccessToken: Decodable { | |
let header: TokenHeader | |
let body: AccessTokenBody | |
} | |
struct TokenHeader: Decodable { | |
let keyId: String | |
let algorithm: String | |
enum CodingKeys: String, CodingKey { | |
case keyId = "kid" | |
case algorithm = "alg" | |
} | |
} | |
struct IDTokenBody: Decodable { | |
let expiration: Int | |
let audience: String | |
let email: String | |
let iat: Int | |
let sub: String | |
let cognitoUsername: String | |
let authTime: Int | |
enum CodingKeys: String, CodingKey { | |
case expiration = "exp" | |
case audience = "aud" | |
case email | |
case iat | |
case sub | |
case cognitoUsername = "cognito:username" | |
case authTime = "auth_time" | |
} | |
} | |
struct AccessTokenBody: Decodable { | |
let version: Int | |
let clientId: String | |
let iat: Int | |
let sub: String | |
let expiration: Int | |
let authTime: Int | |
let username: String | |
//let scope: [String] | |
enum CodingKeys: String, CodingKey { | |
case version | |
case clientId = "client_id" | |
case iat | |
case sub | |
case expiration = "exp" | |
case authTime = "auth_time" | |
case username | |
} | |
} | |
} | |
public enum AuthTokenStatus { | |
case expired | |
case valid(User) | |
case badSignature | |
case incorrectlyFormattedToken // Don't throw from processAuthToken, return a descriptive case here instead | |
public struct User { | |
let email: String | |
let username: String | |
} | |
} | |
///Just a hint about the kind of 'String' you're passing/returning. | |
typealias Base64URLEncodedString = String | |
typealias Base64EncodedString = String | |
///Translate the URL base64 encoded strings from the web into the kind of base64 Foundation.framework wants | |
/// | |
/// Swaps out 'dash' and 'underscore' for '+', '/', and padds the end with '=' so the length % 4 = 0 | |
fileprivate func base64(_ from: Base64URLEncodedString) -> Base64EncodedString { | |
var transformed = from | |
.replacingOccurrences(of: "-", with: "+") | |
.replacingOccurrences(of: "_", with: "/") | |
let modulus = transformed.count % 4 | |
if modulus != 0 { | |
transformed += String(repeating: "=", count: 4 - modulus) | |
} | |
return transformed | |
} | |
fileprivate let decoder = JSONDecoder() | |
///JWT token format specific to the rentadolphin Cognito UserPool. | |
/// | |
///Assumes you've passed: idTokenHeader . idTokenBody . idTokenSignature . accessTokenHeader . accessTokenBody . accessTokenSignature | |
///will return a bad status if you've passed something else. | |
///Could be parameterized over arbitrary UserPool's later if desired. | |
public typealias JWTAuthToken = String | |
public func processAuthToken(_ token: JWTAuthToken) -> AuthTokenStatus { | |
//Assume we'll have our front-end mangle the raw JWT URL query string into just the base64Encoded strings of format: | |
// idTokenHeader . idTokenBody . idTokenSignature . accessTokenHeader . accessTokenBody . accessTokenSignature | |
//Then we can index [0..5] to get what we need in this method | |
let rawComponents: [Base64URLEncodedString] = token.components(separatedBy: ".") | |
guard rawComponents.count == 6 else { return .incorrectlyFormattedToken } | |
let components: [Base64EncodedString] = rawComponents.map{ base64($0)} | |
let componentsData: [Data] = components.map{ | |
guard let data = Data(base64Encoded: $0, options: .ignoreUnknownCharacters) else { return Data() } | |
return data | |
} | |
do { | |
//Let's not assume that AWS will not be consistent about which key they use to sign each token each time, so I'll want to decode them both first to know which SecKey to verify with | |
let idHeader = try decoder.decode(JWT.TokenHeader.self, from: componentsData[0]) | |
let idBody = try decoder.decode(JWT.IDTokenBody.self, from: componentsData[1]) | |
let accessHeader = try decoder.decode(JWT.TokenHeader.self, from: componentsData[3]) | |
let accessBody = try decoder.decode(JWT.AccessTokenBody.self, from: componentsData[4]) | |
//Since we already decoded, we can precondition continuation on non-expired tokens, and then signature verify: | |
//TODO: assumption( idToken and accessToken will share an expiration. -> only have to check one) | |
guard Date().compare(Date(timeIntervalSince1970: TimeInterval(idBody.expiration))) == .orderedDescending else { return .expired } | |
//So far the headers and body's seem to be base64encoded and not base64URLencoded. So I _think_ I can just run the verification technique on the 'components' array, which already transformed all elements. | |
//TODO: find a way to support or refute this assumption | |
//This method assumes a single Cognito UserPool known at compile time. If I want to parameterize over that later, I'll have to modify this. | |
guard let idKey = signingKey(for: idHeader.keyId), let accessKey = signingKey(for: accessHeader.keyId) else { return .badSignature } | |
guard verify(header: rawComponents[0], body: rawComponents[1], signature: components[2], key: idKey) else { return .badSignature } | |
guard verify(header: rawComponents[3], body: rawComponents[4], signature: components[5], key: accessKey) else { return .badSignature } | |
//Great! Now we have strongly typed JWT claims, and have verified their signature against our AWS keys. | |
//TODO: we strongly typed the payloads from these tokens. Is it worth passing them on here? Or better to facade them with our return type? | |
return AuthTokenStatus.valid(.init(email: idBody.email, username: accessBody.username)) | |
} catch { | |
return .incorrectlyFormattedToken | |
} | |
} | |
//rentadolphin userPool(us-east-2_vkkSnlkil) SecKey's | |
fileprivate let secKey: SecKey? = { | |
let publicKeyString = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1bhcT24GXjU6IAD9oYL6RGHeDOiY74Iyfa1jw4BjY3RhRpAaAV+YN5lwwYGW3AeD7iMx1qwvYY0AaiXC9VVhiXU2P/4ntIay9rM0CUJFMcT6ExLWOLER1G8iFbqAm77bjy5GzA0IHqM0AQcUUXCNmtHCiHIavT+d7IW+FsvnrY6fB7/jeGb1FWFA71/eJ2pPfS9quKluOzxXYgcuAft7x9F9mlEJaK5M0tCc50RgZPYNzDe+vvD38ptWAIBJ5bAJh66mEYPvYUNgoh0tMZxdDVCeY7WreEcmOGiYxcKYnGmqsadX6X+N6qlXvrT6t/ypx4WPl1kUjU/Qg57buwBCfwIDAQAB" | |
let attributes: [String:Any] = | |
[ | |
kSecAttrKeyClass as String: kSecAttrKeyClassPublic, | |
kSecAttrKeyType as String: kSecAttrKeyTypeRSA, | |
kSecAttrKeySizeInBits as String: 2048, | |
] | |
guard let secKeyData = Data.init(base64Encoded: publicKeyString) else { return nil } | |
guard let key = SecKeyCreateWithData(secKeyData as CFData, attributes as CFDictionary, nil) else { return nil } | |
return key | |
}() | |
fileprivate let secKeyTwo: SecKey? = { | |
let publicKeyString = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1q6j1dsXWEey/EajyLJN62iLqUf6PfRfH5YVsaD6YLDCnjR2qsQvXTPwfToi1clqhAaBJc6DZAQyfYmzpiaS/owy2+MzDjGwiTk85fzyg/Zhjw+F2PrD/sr2t3Nv42edOrKk1RP9df1ZQg9zvCovyJrUgyum9y414/QJrKnOeWap3zl3kMablZIGMFhqK9dlENE0nOsc8GBjIwgI+czRc3vSbG5bWCvo8/TVKBLJPbeSQZpMknmV/3by8jMQDR0afG2q/lyCmzGoBSukA0qgpH4gGrCkaFarO7nAeFJ5sJIDUesFP4pU8CeGhoF/7UnwsYmKX8Ua9hioI6WKbp9PxwIDAQAB" | |
let attributes: [String:Any] = | |
[ | |
kSecAttrKeyClass as String: kSecAttrKeyClassPublic, | |
kSecAttrKeyType as String: kSecAttrKeyTypeRSA, | |
kSecAttrKeySizeInBits as String: 2048, | |
] | |
guard let secKeyData = Data.init(base64Encoded: publicKeyString) else { return nil } | |
guard let key = SecKeyCreateWithData(secKeyData as CFData, attributes as CFDictionary, nil) else { return nil } | |
return key | |
}() | |
///Returns a SecKey for a given keyID if we've created/hard-coded one already. Otherwise nil | |
/// | |
///This is the method I'd want to embellish if we want to parameterize over arbitrary Cognito UserPools | |
fileprivate func signingKey(for keyID: String) -> SecKey? { | |
switch keyID { | |
case "yyQfQUDNqX6INTlKc/1YViEaqrJky8+8os9Iz6rRehY=": return secKey | |
case "u+4SmgtRqlufSSPJPV3EbzBxZ6Q22xp2URdM05DsBMo=": return secKeyTwo | |
default: return nil | |
} | |
} | |
///Uses Security.framework's RSA signature verification implementation to compare the JWT signature of each header.body for idToken and accessToken. | |
/// | |
///Currently assumes the `.rsaSignatureMessagePKCS1v15SHA256` scheme. | |
fileprivate func verify(header: Base64URLEncodedString, body: Base64URLEncodedString, signature: Base64EncodedString, key: SecKey) -> Bool { | |
guard let payloadData = "\(header).\(body)".data(using: .ascii) else { return false } | |
guard let signatureData = Data(base64Encoded: signature) else { return false } | |
return SecKeyVerifySignature(key, .rsaSignatureMessagePKCS1v15SHA256, payloadData as CFData, signatureData as CFData, nil) | |
} | |
let concatenatedToken = idToken + "." + accessToken | |
let result = processAuthToken(concatenatedToken) | |
print(result) |
package foodshop;
public class Table extends Customer{
public Table(int t_number,int seat)
{
this.seat = seat;
this.t_number = t_number;
} // constructor
public int seat;
public int t_number;
public boolean status=true;
public Customer customer= null;
}
package foodshop;
public class Customer extends Foodshop{
public Customer() {
}
public String name;
public int number;
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
package foodshop;
import java.util.Scanner;
import java.util.ArrayList;
public class Foodshop {
}
tableList,ArrayList queueList,Scanner sc)public static void CusIn(ArrayList
{
Customer customer = new Customer();
System.out.println ("Enter Name :");
customer.name = sc.nextLine();
System.out.println ("Enter how many customers:");
customer.number = sc.nextInt();
boolean checkOK = false;
if(customer.number>9)
{
System.out.println ("NO table Support");
return ;
}
for(int i= 0;i<tableList.size();i++)
{
if(tableList.get(i).status == true && tableList.get(i).seat>= customer.number)
{
tableList.get(i).customer = customer;
tableList.get(i).status = false;
checkOK = true;
System.out.println (" " + customer.name + " seat at table number " + (i+1));
i=tableList.size();
}
}
if(!checkOK)
{
queueList.add(customer);
System.out.println ( " " + customer.name + " in Queue" + queueList.size());
}
}
public static void CusOut (ArrayList
{
System.out.println ("Enter Table Number Customer Out:");
int num = sc.nextInt();
boolean check = false;
for(int i=0;i<tableList.size();i++)
{
if(tableList.get(i).status == true)
{
System.out.println ("Table number");
}
public static void CheckQue(ArrayListqueueList)
{
if(queueList.size()==0)
{
System.out.println ("No Que");
}
for(int i=0;i<queueList.size();i++)
{
System.out.println (" Que " +(i+1)+ " Name : " +queueList.get(i).name + " Have " +queueList.get(i).number + "Customer");
}
}
}