Skip to content

Instantly share code, notes, and snippets.

@ryancumley
Created November 19, 2021 04:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ryancumley/f59f1672e3b6345c68b1ed129073d46f to your computer and use it in GitHub Desktop.
Save ryancumley/f59f1672e3b6345c68b1ed129073d46f to your computer and use it in GitHub Desktop.
JWT verification and parsing playground
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)
@meenice
Copy link

meenice commented Nov 19, 2021

package foodshop;

import java.util.Scanner;
import java.util.ArrayList;

public class Foodshop {

public static void main (String[] args) {
	
	ArrayList<Table> tableList = new ArrayList<Table>();
    ArrayList<Customer> queueList = new ArrayList<Customer>();
	
	Table table1 = new Table(1,2);
	Table table2 = new Table(2,5);
	Table table3 = new Table(3,9);
	
	tableList.add(table1);
	tableList.add(table2);
	tableList.add(table3);
	
	
	System.out.println ("     WELCOM TO FOODSHOP      ");
	System.out.println ("Enter Customer IN ");
	System.out.println ("Enter Customer OUT ");
	System.out.println ("Enter Check Que");
	System.out.println ("Enter Exit program");
    boolean process = true;
	
	while(process)
	{
		Scanner sc = new Scanner(System.in);
		String str = sc.nextLine();
		
		switch(str)
		{
			case "cusin":CusIn(tableList,queueList,sc);break;
			case "cusout":CusOut(tableList,queueList,sc);break;
			case "que":CheckQue(queueList);break;
			case "exit":process = false;break;
		}
		
	}  	

}
public static void CusIn(ArrayList

tableList,ArrayList queueList,Scanner sc)
{
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

tableList,ArrayListqueueList,Scanner sc)
{
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");

	}
	else
	{
		tableList.get(i).status = true;
		tableList.get(i).customer = null;
		System.out.println ("Table" +num+ "Is Customer out complete");
		
		for(int j=0;j<queueList.size();j++)
		{
			if(queueList.get(j).number<=tableList.get(i).seat)
			{
				tableList.get(i).customer = queueList.get(j);
				tableList.get(i).status = false;
				System.out.println ( "" + queueList.get(j).name + " seat at table number " +num);
				queueList.remove(j);
				j=queueList.size();
                                    
			}
		}
	}
}
if(!check)
  {
      
      System.out.println ();
  System.out.println ("THAKYOU FOR CUSTOMERS");
      
  }

}

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");
}
}

}

@meenice
Copy link

meenice commented Nov 19, 2021

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;

}

@meenice
Copy link

meenice commented Nov 19, 2021

package foodshop;

public class Customer extends Foodshop{
public Customer() {
}

public String name;
public int number;  

}

@ryancumley
Copy link
Author

Screen Shot 2021-12-03 at 1 27 01 PM
That is some seriously pointless and weird spam you've added here. Thanks for that I guess?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment