3

Got the credentials for Service Account from Developer Console

First, I converted p12 private key to PEM:

openssl pkcs12 -in <private key for Service Account>.p12 -out calendar.key -nocerts -nodes

Then I run:

use MIME::Base64;
use Crypt::OpenSSL::RSA;
use File::Slurp;

my $header = encode_base64('{"alg":"RS256","typ":"JWT"}','');
my $claim = encode_base64('{
"iss":"<mail for the Service Account>",
"scope":"https://www.googleapis.com/auth/calendar",
"aud":"https://accounts.google.com/o/oauth2/token",
"exp":'.(time()+3600).',
"iat":'.time().'
}','');

my $key = read_file('calendar.key');
my $rsa = Crypt::OpenSSL::RSA->new_private_key($key);
$rsa->use_sha256_hash;
$rsa->use_pkcs1_padding;
my $signature = encode_base64($rsa->sign($header . '.' . $claim), '');

my $token_request = $header . '.' . $claim . '.' . $signature;

print `curl -d 'grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=$token_request' https://accounts.google.com/o/oauth2/token`;

I get

{
  "error" : "invalid_grant"
}

I synchronized the system time with NTP, didn't help.

Miller
  • 34,962
  • 4
  • 39
  • 60
bgcc
  • 61
  • 4

4 Answers4

3

I don't know why, but the problem was with curl.

I replaced it by WWW::Mechanize:

my $mech = WWW::Mechanize->new( autocheck => 1 );
$mech->post('https://accounts.google.com/o/oauth2/token',
    'Content-Type' => 'application/x-www-form-urlencoded',
    'Content' => [
        'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
        'assertion' => $token_request,
    ],
);

and it works.

Miller
  • 34,962
  • 4
  • 39
  • 60
bgcc
  • 61
  • 4
2

Just in case anybody comes here looking for an approach to obtaining a bearer token from Google Service Account Credentials in the currently provided JSON file; here's a snippet that worked for me after I couldn't use Crypt::JWT or Mojo::JWT::Google.

use LWP::UserAgent;
use JSON;
use  Mojo::JWT;
use Mojo::File;

my $jwt = create_jwt_from_path_and_scopes( 'path/to/credentials.json', 'email https://www.googleapis.com/auth/compute' );
my $ua  = LWP::UserAgent->new();

my $response = $ua->post('https://www.googleapis.com/oauth2/v4/token', 
                {   'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', 
                    'assertion'  =>  $jwt 
                }
            ); 

#######################################################################
sub create_jwt_from_path_and_scopes
{
  my ( $path, $scope ) = @_;
  croak("No path provided")        if not defined $path;
  croak("$path no available")      if not -f $path;
  my $json = decode_json( Mojo::File->new($path)->slurp );
  croak("No Private key in $path") if not defined $json->{private_key};
  croak("Not a service account")   if $json->{type} ne 'service_account';
  my $jwt = Mojo::JWT->new();
  $jwt->algorithm('RS256');
  $jwt->secret($json->{private_key});

  $jwt->claims( {
      iss   => $json->{client_email},
      scope => $scope,
      aud   => 'https://www.googleapis.com/oauth2/v4/token',   
      iat   => time(),
      exp   => time()+3600   
  } );
  $jwt->set_iat( 1 );
  return $jwt->encode;
}
#######################################################################
Peter Scott
  • 1,318
  • 12
  • 19
  • I'm facing a similar problem. What was the exact reason that you couldn't use Crypt::JWT? I keep getting an odd dependency error, saying that no loadable CryptX could be found. – procra Apr 02 '19 at 13:30
  • I which I could give you 100 points ... even I was not searching for the same answer ... Anyways just a sidenote - one could generate the keys in bash as follows: `jwt_private_key_file=~/.ssh/jwtRS256.key; jwt_public_key_file=~/.ssh/jwtRS256.key.pub; ssh-keygen -t rsa -b 4096 -m PEM -f $jwt_private_key_file ; openssl rsa -in $jwt_private_key_file -pubout -outform PEM -out $jwt_public_key_file` – Yordan Georgiev Mar 12 '20 at 20:37
1

Change your curl command to be:

curl -H "Content-Type: application/x-www-form-urlencoded" \
     -d grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer \
     -d assertion=$token_request \
      https://accounts.google.com/oauth2/token
Jay Lee
  • 13,415
  • 3
  • 28
  • 59
0

Try to use BASE64URL instead of BASE64. JWT/JWS/JWE specifications use BASE64URL.

MIME::Base64 ---> MIME::Base64::URLSafe
encode_base64 ---> urlsafe_b64encode
Takahiko Kawasaki
  • 18,118
  • 9
  • 62
  • 105