Signing a JWT header for Google OAuth2

Difference between version 6 and 7 - Previous - Next
According to the documentation at https://developers.google.com/accounts/docs/OAuth2ServiceAccount%|%Using OAuth 2.0 for Server to Server Applications%|%
===
The signing algorithm in the JWT header must be used when computing the signature. 
The only signing algorithm supported by the Google OAuth 2.0 Authorization Server is 
RSA using SHA-256 hashing algorithm. This is expressed as ‘RS256’ in the ‘alg’ field in the JWT header.

Sign the UTF-8 representation of the input using SHA256withRSA 
(also known as RSASSA-PKCS1-V1_5-SIGN with the SHA-256 hash function) with the private key 
obtained from the Google Cloud Console. The output will be a byte array.
===

What is there in the tcl space that is equivalent to `SHA256withRSA (also known as RSASSA-PKCS1-V1_5-SIGN with the SHA-256 hash function)`? I was thinking that the `sha256` package would do the trick, but now I'm not so sure. Reason is that I took the signature block given on the developers.google.com page and did a `::base64::decode`. This gave me a string of assorted low and high ascii, which is not what I'm getting with a `::sha2::hmac`

The goal is to be able to set up an OAuth2 connection to Google Analytics. I've already been to the https://cloud.google.com/console%|%Google Cloud Console%|% to set up the account, and get the various keys. The code I'm using is below. I'm still at the point where you generate the header. I haven't got to the HTTP stuff yet. 

======
package require json
package require sha256
package require base64

set h [open "client_secret.json" r]
set d [read $h]
close $h

set mydd [dict create {*}[lindex [::json::json2dict $d] 1]]

set header [string map {\n "" "=" ""} [::base64::encode "{\"alg\":\"RS256\",\"typ\":\"JWT\"}"]]
set claims [string map {\n "" "=" ""} [::base64::encode "{\
\"iss\":\"[dict get $mydd client_email]\",\
\"scope\":\"https://www.googleapis.com/auth/analytics.readonly\",\
\"aud\":\"https://accounts.google.com/o/oauth2/token\",\
\"exp\":[expr [clock seconds] + 3600],\
\"iat\":[clock seconds]\
}"]]

set signature "$header.$claims"

set h [open "privatekey.p12" r]
fconfigure $h -translation binary
set d [read $h]
close $h

set sig [string map {\n "" "=" ""} [::base64::encode [::sha2::hmac $d $signature]]]
set final "$signature.$sig"

puts $final

======

[APN] See if http://core.tcl.tk/tcllib/doc/trunk/embedded/www/tcllib/files/modules/pki/pki.html#3%|%pki::sign%|% from the pki package in [tcllib] does what you want.

[Bovine] We got this working, and the pki module was indeed the key:
======

package require yajltcl
package require sha256
package require base64
package require http
package require tls
package require pki

proc base64_url_encode {input} {
        return [string map {\n "" "=" "" + - / _} [::base64::encode $input]]
}


set header {{"alg":"RS256","typ":"JWT"}}
set header [base64_url_encode $header]


set x [yajl create #auto]
$x map_open \
        string iss string "[email protected]" \
        string scope string "https://www.googleapis.com/auth/prediction" \
        string aud string "https://accounts.google.com/o/oauth2/token" \
        string exp number [expr {[clock seconds] + 3600}] \
        string iat number [clock seconds] \
        map_close
set claims [base64_url_encode [$x get]]
$x delete

set signature "$header.$claims"


set fd [open "privatekey.pem" "r"]
set keydata [read $fd]
close $fd


set key [::pki::pkcs::parse_key $keydata "this_is_my_pem_password"]
set sig [base64_url_encode [::pki::sign $signature $key sha256]]

set final "$signature.$sig"


set postdata [::http::formatQuery grant_type "urn:ietf:params:oauth:grant-type:jwt-bearer" assertion $final]


::http::register https 443 ::tls::socket
set fp [::http::geturl "https://accounts.google.com/o/oauth2/token" -query $postdata]
set status [::http::status $fp]
set ncode [::http::ncode $fp]
set html [::http::data $fp]
::http::cleanup $fp

array set token [::yajl::json2dict $html]
parray token


======[wiwo] The latest Google JSON credentials provide the key in PEM format. ::pki::parse_key expects the key in RSA format. Openssl to the rescue:

======
openssl rsa -in pk.key -out pk-rsa.key

======

<<categories>> Security