identity 1.0 documentation

JOSE JWS:Implementation

«  ID Token:Implementation   ::   Contents

JOSE JWS:Implementation

Todo

  • ECDSA must be implemented

JWS Token

base64url

Python

def to_base64url(src):
    return base64.urlsafe_b64encode(src).replace('=','')

def from_base64url(src):
    return base64.urlsafe_b64decode(src + '=' * (len(src) % 4 ))

C#:

public static string ToBase64Url(byte[] input)
{
    StringBuilder result = new StringBuilder(
                   Convert.ToBase64String(input).TrimEnd('='));
    result.Replace('+', '-');
    result.Replace('/', '_');
    return result.ToString();
}

public static byte[] FromBase64Url(string base64ForUrlInput)
{
    int padChars = (base64ForUrlInput.Length % 4) == 0 ? 0
                 : (4 - (base64ForUrlInput.Length % 4));
    StringBuilder result = new StringBuilder(
                               base64ForUrlInput,
                               base64ForUrlInput.Length + padChars);
    result.Append(String.Empty.PadRight(padChars, '='));
    result.Replace('-', '+');
    result.Replace('_', '/');
    return Convert.FromBase64String(result.ToString());
}

Token

jws_create_token()

Python
import json

def jws_create_token(header,payload ,secret,pem_private_key):
    '''
        :param header: JWS Header JSON
        :param payload: JWS Payload in JSON
        :param secret: Shared Secret for HMAC
        :param pem_private_key: RSA/DSA Private Key in PEM format
    '''
    jws_header= json.dumps( header )        #dumps produdes utf8 string by default
    jws_payload= json.dumps( payload )

    encoded_jws_header = to_base64url(jws_header)
    encoded_jws_payload= to_base64url(jws_payload)

    jws_secured_input = "%s.%s" % ( encoded_jws_header, encoded_jws_payload )
    jws_signature = _sign(secret,pem_private_key,
                            header['alg'],
                            jws_secured_input )

    encoded_jws_signature = to_base64url(jws_signature)
    jws_token = "%s.%s.%s" % ( encoded_jws_header,encoded_jws_payload, encoded_jws_signature )

    return jws_token
#  alg : [HRE]S[256|384|512]

token = create_jws_token_rsa(
        {"typ":"JWT","alg":"RS256"},                    #:Header
        {"iss":"alice","aud":"bob","user_id":"charlie"},#:Payload
        secret,                                         #:for HMAC
        Idp.objects.get(issuer="me").private_key        #: Load private key
        )
C#
public static string JwsCreateToken(
    Dictionary<string,object> header,
    object payload,
    string secret,
    string pem_private_key )
{
    string jws_header = JsonConvert.SerializeObject(header);
    string jws_payload = JsonConvert.SerializeObject(payload);

    string encoded_jws_header = Jose.Utils.ToBase64Url( Encoding.UTF8.GetBytes(jws_header));
    string encoded_jws_payload = Jose.Utils.ToBase64Url(Encoding.UTF8.GetBytes(jws_payload));

    string jws_secured_input = encoded_jws_header + "." + encoded_jws_payload;

    byte[] jws_signature = _sign(secrete,
                                pem_private_key,
                                (string)header["alg"],
                                Encoding.UTF8.GetBytes(jws_secured_input));

    string encoded_jws_signature = Jose.Utils.ToBase64Url(jws_signature);

    return string.Format("{0}.{1}.{2}",
                encoded_jws_header, encoded_jws_payload, encoded_jws_signature);
}
// just a sample
Dictionary<string, object> header = new Dictionary<string, object>()
                                        { { "typ", "JWT" },
                                          { "alg", "RS256" } };
Dictionary<string, object> payload = new Dictionary<string, object>()
                                        { { "iss", "alice" },
                                          { "aud", "bob" },
                                          { "user_id", "charlie" } };
// for RSA ( Shared Secret in null )
string token = Jose.Jws.CreateTokenRsa( header,
                                        payload,
                                        null,
                                        GetIdpByName('alice").private_key);

jws_verify_token()

Python
def jws_verify_token(jws_token,secret,pem_x509):
    '''
        :param jws_token:  JWS Token
        :param secret:  Shared Secret for HMAC
        :param pem_x509: X.509 Certificate for RSA, ECDSA
    '''
    (encoded_jws_header,encoded_jws_payload,encoded_jws_signature) = jws_token.split('.')

    jws_header = from_base64url(encoded_jws_header)
    jws_payload = from_base64url(encoded_jws_payload)
    jws_signature=from_base64url(encoded_jws_signature)

    jws_secured_input = "%s.%s" % ( encoded_jws_header, encoded_jws_payload )
    header = json.loads(jws_header)

    is_valid = _verify(secret,pem_x509,header['alg'],jws_secured_input, jws_signature )

    return is_valid==1,header,json.loads(jws_payload)

Todo

pem_x509 can be None for session wise certificate negotiation. There should be the other utility which fetch and validate the certficate based on header decoded from jws_token .

(is_valid,header,payload) = verify_jws_token(
                                token,
                                secret,
                                Op.objects.get(issuer="alice").x509_cert_cache )
C#
public static bool JwsVerifyToken(string jws_token, string,secret,string pem_x509)
{
    Match m=  (new Regex(@"^(?<encoded_jws_header>.+)\.(?<encoded_jws_payload>.+)\.(?<encoded_jws_signature>.+)$")).Match(jws_token);
    string jws_header = Encoding.UTF8.GetString(
                            Jose.Utils.FromBase64Url(m.Groups["encoded_jws_header"].Value)) ;
    string jws_payload = Encoding.UTF8.GetString(
                            Jose.Utils.FromBase64Url(m.Groups["encoded_jws_header"].Value)) ;
    byte[] jws_signature = Jose.Utils.FromBase64Url(m.Groups["encoded_jws_signature"].Value);

    string jws_secured_input = m.Groups["encoded_jws_header"].Value + "." +  m.Groups["encoded_jws_payload"].Value;

    Dictionary<string,object> header = JsonConvert.DeserializeObject<Dictionary<string,object>>(jws_header);

    bool ret = _verify(pem_x509, (string)header["alg"],
                   Encoding.UTF8.GetBytes(jws_secured_input),
                    jws_signature);

    return ret;
}
// just a sample
bool ret = Jose.Jws.VerifyTokenRsa(token, null,GetOpByName("alice").x509_cache);

So what is _sign() and _verify() ? Actula funcitons will vary based on “alg”orithm paramter: rsa_sign() and rsa_vefiy() for RSA, hmac_sign() and hamc_verify for HMAC.

RSA and X.509

Key negotiation in advance

Any RP and OP can negotiate their private keys and X.509 certificate in advance before a OpenID Connect session begins.

RP : Registartion

OP:Discovery Information

Session-wise Key Negotiation

Any RP can specifiy its X.509 certificate for the private key used to sign a OpenID Request Object. Also any OP can specify its X.509 certificate for the private key used to sign tokens or response data. To do those, JWS header provides the following X.509 parameters. {See 4.1. Reserved Header Parameter Names )

JWS X.509 Header Parameters
x5u URL, like as x509_url in OpenID Connect Dynamic Client Registration 1.0 and OpenID Connect Discovery 1.0. Format of data returned by this URL is PEM
x5c X.509 certificate(chain) used this JWS base64-encode DER/BER ( NOT in base64url )
x5t SHA-1 thumprint of :DER (binary) encoded X.509 certificate base64url encoded thumprint

Fingerprint of specifed PEM certificate.

Python

from M2Crypto import X509
import utils
def x5t(cert_string,format=0):
    ''' format
        = 0 (M2Crypto.X509.DER_FORMAT)
        = 1 (M2Crypto.X509.PEM_FORMAT)
    '''
    cert = X509.load_cert_string(cert_string,format)
    return utils.to_base64url(
            utils.from_hex_string(
                cert.get_fingerprint(md='sha1')) )

C#

public static string GetFingerprint(X509Certificate cert,
                                        string digester_name="SHA1")
{

    IDigest digester = DigestUtilities.GetDigest(digester_name);
    byte[] der = cert.GetEncoded();

    digester.BlockUpdate(der, 0, der.Length);

    byte[] hash = new byte[digester.GetDigestSize()];
    digester.DoFinal(hash, 0);

    return Jose.Utils.ToBase64Url(hash);
}

public static string GetFingerprint(string pem_x509,string
                                        digerster_name="SHA1")
{
    return GetFingerprint(CertificateFromPem(pem_x509), digerster_name);
}

Sign/Verify with RSA Key and X.509 Certificate

sha_name()

  • select proper SHA digest function for alg .
Python
import re

def sha_name(alg):
    '''
        :param alg: Algorithms defined in JWA
    '''
    return "sha%(bits)s" % re.search(r'[HRE]S(?P<bits>\d+)$',alg).groupdict()
C#
// Look at "security/SignerUtilities.cs" of BouncyCastle
// for RSA signature algorithm.

public static string SignerName(string alg)
{
    Regex re= new Regex("^RS(?<bits>\\d+)$");
    Match m = re.Match(alg);
    if( m!= null ){
        return string.Format("SHA{0}WITHRSA", m.Groups["bits"].Value);
    }
    return "";
}

rsa_sign()

Python
from M2Crypto import RSA,util
import hashlib

def rsa_sign(secret,private_key_pem, alg, secure_input ,passphrase=None):
    '''
        :param secret: ignored for RSA
        :param private_key_pem: PEM string of private key of signer
        :param alg: Algorithms defined in JWA
        :param secure_input: Message for digest input
        :param passphrase: (optional) pass phrase for private key

    '''
    def _passphrase():
        return passphrase

    callback = _passphrase if passphrase else util.passphrase_callback
    key = RSA.load_key_string(private_key_pem,callback=callback )

    sha=sha_name(alg)

    return key.sign( getattr(hashlib,sha)(secure_input).digest(),sha)
C#
public static byte[] RsaSign(string secret,string key_pem,
            string alg, byte[] secure_input ,string passphrase)
{
    // secret is isngored for RSA

    var signer = SignerUtilities.GetSigner(SignerName(alg));
    signer.Init(true, RsaPrivateKeyFromPem(key_pem));
    signer.BlockUpdate(secure_input, 0, secure_input.Length);

    return signer.GenerateSignature();
}

Todo

Find to load PEM private key secured with pass phrase.

rsa_verify()

Python
from M2Crypto import X509

def rsa_verify(secret,x509_pem, alg, secure_input ,signature):
    '''
        :param secret: ignored for RSA
        :param x509_pem: PEM string of signer's X.509 certificate
        :param alg: Algorithms defined in JWA
        :param secure_input: Message for digest input
        :param signature: signature which OP has signed.
    '''

    pub= X509.load_cert_string(x509_pem).get_pubkey()
    sha=sha_name(alg)
    return pub.get_rsa().verify( getattr(hashlib,sha)(secure_input).digest(),signature,sha)
C#
public static bool RsaVerify(string secret, // ignored for RSA
                        string x509_pem,
                        string alg,
                        byte[] secure_input, byte[] signature)
{
    RsaKeyParameters pubkey = (RsaKeyParameters)CertificateFromPem(x509_pem).GetPublicKey();

    var signer = SignerUtilities.GetSigner(SignerName(alg));
    signer.Init(false, pubkey);
    signer.BlockUpdate(secure_input, 0, secure_input.Length);

    return signer.VerifySignature(signature);
}

PEM in C#

Because it’s not easy to read PEM formatted data in Microsoft library, BouncyCastle can be used to load X.509 certificate and private key.

X.509 Certificate im PEM
public static X509Certificate CertificateFromPem(string certificate)
{
    TextReader c = new StringReader(certificate);
    PemReader pem2 = new PemReader(c);
    X509Certificate x509 = (X509Certificate)pem2.ReadObject();

    return x509;
}
DER Private Key im PEM
public static RsaKeyParameters RsaPrivateKeyFromPem(string private_key )
{
    TextReader r = new StringReader(private_key);
    PemReader pem = new PemReader(r);
    return (RsaKeyParameters)((AsymmetricCipherKeyPair )pem.ReadObject()).Private;
}

HMAC and Shared Secret

Signature?

No. JWS Signature for HMAC is not signature actually. Verifier must repeat the same hmac_sign() process as creator did and check if JWS Signature value produced by the verifier is exactly same as the one received.

hmac_sign()

Python

import hmac,hashlib

def digester(alg):
    '''
        :param alg: Algorithms defined in JWA
    '''
    return getattr(hashlib,
            "sha%(bits)s" % re.search(r'[HRE]S(?P<bits>\d+)$',alg).groupdict())

def hmac_sign(secret,pem,alg,data ):
    '''
        :param secret: shared secret
        :param pem: ignored by HMAC
        :param alg: JWA algorithm
        :param data: data to be signed.
    '''
    return hmac.new(key,data,digester(alg) ).digest()

C#

// BouncyCastle

public static IMac GetMac(string alg)
{
    Regex re = new Regex("^(?<alg>[H])S(?<bits>\\d+)$");
    Match m = re.Match(alg);
    if (m != null)
        return MacUtilities.GetMac(
                    string.Format("HMAC-SHA{0}",
                            m.Groups["bits"].Value ));
    return null;
}

public static byte[] HmacSign(
            string key,
            string pem,     //ignored for HMAC
            string alg,
            byte[] data)
{
    IMac mac = GetMac(alg);

    byte[] signature = new byte[mac.GetMacSize()];

    mac.Init( new KeyParameter(Encoding.UTF8.GetBytes(key) ));
    mac.BlockUpdate(data, 0, data.Length);
    mac.DoFinal(signature,0);

    return signature;
}

hmac_verify()

Python

def hmac_verify(secret,pem,alg,data,signature):
    '''
        :param secret: shared secret
        :param pem: ignored by HMAC
        :param alg : JWA algorithm
        :param data: data signed
        :param signature: produced signature
    '''
    #: hmac_sign is used by a verifier to check if same or not

    return signature == hmac_sign(secret,alg,data )

C#

public static bool HmacVerify(
        string key,
        string pem, // ignored for HMAC
        string alg,
        byte[] data, byte[] signature)
{
    return signature.SequenceEqual( HmacSign(key, alg, data ));
}

Libraries

Python

# standard python
import base64
import hashlib
import json

# M2Crypto
from M2Crypto import RSA
from M2Crypto import X509
from M2Crypto import util

C#

// Micsosoft
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Security.Cryptography;

// BouncyCastle
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Signers;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.X509;
using Org.BouncyCastle.Security;

// Json.NET
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

«  ID Token:Implementation   ::   Contents