Cannot get signed url to work with gcloud storage - php

Cannot get signed url to work with gcloud storage.
I know there is probably something simple that's missing here but I can't find it.
Trying to create a signed url to a file in a gcloud bucket following the directions here https://cloud.google.com/storage/docs/access-control/create-signed-urls-program
Created a servive account and downloaded the pk12 and converted p12 to pem to get the key.
Here is code:
$pkeyid = "-----BEGIN RSA PRIVATE KEY-----
[removed actual key]
-----END RSA PRIVATE KEY-----";
$secret = "notasecret";
$expires = time()+86400;
$http_method = "GET";
$bucketName = "mybucketname";
$stringtosign = "GET\n
\n
text/plain\n
".$expires."\n
\n
\n
\n
".$bucketName ."/mymediafile.mp4";
openssl_sign($stringtosign, $signature, $pkeyid);
$emailid="serviceaccount#[project].iam.gserviceaccount.com";
$signature = urlencode(base64_encode($signature));
$gcloudloc = "https://storage.googleapis.com/".$bucketName ."/mymediafile.mp4?GoogleAccessId=".$emailid."&Expires=".$expires."&Signature=".$signature;
I get what looks like a valid signiture, but when I try to use the full url I get
The request signature we calculated does not match the signature you provided. Check your Google secret key and signing method.

I had two issues:
Pasting in the Private Key.
Missing some parameters in the openssl_sign
Also my $stringtosign seems to mess it up. Changing to all one line fixed that part.
I found this "Google Cloud Storage Signed Url for media" and was able to use the function (below) from hdezela to get it working!
function storageURL($bucket,$archivo) {
$expires = time()+60;
$to_sign = ("GET\n\n\n".$expires."\n/".$bucket.'/'.$archivo);
$fp = fopen('/path/to/google.pem', 'r');
$priv_key = fread($fp, 8192);
fclose($fp);
$pkeyid = openssl_get_privatekey($priv_key);
if(!openssl_sign($to_sign,$signature,$pkeyid,'sha256')) {
$signature = 'sinfirma';
} else {
$signature = urlencode(base64_encode($signature));
}
return ('https://'.$bucket.'.storage.googleapis.com/'.$archivo.'?GoogleAccessId=XXXXXXX#developer.gserviceaccount.com&Expires='.$expires.'&Signature='.$signature);
}
I originally thought it was the url but this works as well.
"https://storage.googleapis.com/".$bucketName ."/

Related

How to get R and S values from ECDSA signature using PHP

I am working on Signature of data using ECDSA from Starkbank library and I can get the base64 format of the signature value MEUCIALlD6Xsd0Xdj7XTrD2gP4Q3PlssTxLOCUi6R8FbXMlbAiEAmW8HLiBnhaBBPzIL64FGzFYzUwF1HfX+a8ep5/NpI0k= and the Der value is 0E ���wEݏ�Ӭ=�?�7>[,O� H�G�[\�[!�o. g��A?2�F�V3Su��kǩ��i#I
but I want to know the R and S values with the length as well, how can I achieve it?
my PHP code is:
<?php
require_once "src/ellipticcurve.php";
#privateKey from PEM string
$privateKey = EllipticCurve\PrivateKey::fromPem("
-----BEGIN EC PRIVATE KEY-----
MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgPUAdAJuELXoxEumrKUPd
yHLP9bITTV+yOSw5q1H8W/2hRANCAASW6MSUA/wJRcj0AljN0tnpMBp5ISqTp8j/
rY7C2BXCXyy03V/lP7jn0LSgJvykVyNRPXfA4zjpFRaOUNWUBNuU
-----END EC PRIVATE KEY-----
");
$message = "j4+wDOaRLRQn7oweoCbob1WDaqPRCTHzonn08b+dJr0";
$signature = EllipticCurve\Ecdsa::sign($message, $privateKey);
# Generate Signature in base64. This result can be sent to Stark Bank in header as Digital-Signature parameter
$base64 = $signature->toBase64();
$der = $signature->toDer();
echo "\n" . $der;
echo "\n" . $base64;
$publicKeyPem = EllipticCurve\Utils\File::read("publicKey.pem");
$publicKey = EllipticCurve\PublicKey::fromPem($publicKeyPem);
# To double check if message matches the signature
//$publicKey = $privateKey->publicKey();
echo "\n" . EllipticCurve\Ecdsa::verify($message, $signature, $publicKey);
?>
the ECDSA config which I am working on:
digest_alg = "sha256",
private_key_bits = 2048,
private_key_type = OPENSSL_KEYTYPE_EC,
curve_name = secp256k1,
You Can Use SOP/ASN1 To get the Sequence from DER.
Try https://lapo.it/asn1js to find the Positions from R and S. Use bin2hex($signature->toDer()) to get Hex-Encoded ASN.1
It depends on the Type of your $signature->toDer().
Maybe your Hexstring is already Sequence, then following two ANS.1-Integers (R/S).
Then use for e.g.
$seq = \Sop\ASN1\Type\UnspecifiedType::fromDER($signature->toDer())->asSequence();
$seq->at(0)->asInteger()...
$seq->at(1)->asInteger()...

How to properly encrypt a JS generated RSA private key with a passphrase?

I have a php back-end that previously generated RSA private/public keypairs on its own, encrypting the private part with a given passphrase.
Now I'm using this library: http://travistidwell.com/jsencrypt/ to generate a keypair on client side. But I didn't find how to encrypt the private key with a passphrase using this library. So I tried using this: http://www.movable-type.co.uk/scripts/aes.html but it seems that a key I get doesn't work, I can't encrypt/decrypt using it on my php back-end and different keys management apps don't recognize the key.
What am I doing wrong and how to successfully encrypt the original JSEncrypt'ed private key properly with a passphrase?
This is how the keypair was generated on PHP:
$config = array(
"digest_alg" => "sha256",
"private_key_bits" => 2048,
"private_key_type" => OPENSSL_KEYTYPE_RSA,
"encrypt_key" => true
);
$keypair = openssl_pkey_new($config);
$pkey_pass = '123';
openssl_pkey_export($keypair, $privKey, $pkey_pass, $config);
$fp = fopen($keys_folder . '/private.pem', 'w');
fwrite($fp, $privKey);
fclose($fp);
$pubKey = openssl_pkey_get_details($keypair);
$fp = fopen($keys_folder . '/public.pem', 'w');
fwrite($fp, $pubKey);
fclose($fp);
Maybe you could adapt code from phpseclib. Quoting it:
if (!empty($this->password) || is_string($this->password)) {
$iv = Random::string(8);
$symkey = pack('H*', md5($this->password . $iv)); // symkey is short for symmetric key
$symkey.= substr(pack('H*', md5($symkey . $this->password . $iv)), 0, 8);
$des = new TripleDES();
$des->setKey($symkey);
$des->setIV($iv);
$iv = strtoupper(bin2hex($iv));
$RSAPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\r\n" .
"Proc-Type: 4,ENCRYPTED\r\n" .
"DEK-Info: DES-EDE3-CBC,$iv\r\n" .
"\r\n" .
chunk_split(base64_encode($des->encrypt($RSAPrivateKey)), 64) .
'-----END RSA PRIVATE KEY-----';
} else {
$RSAPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\r\n" .
chunk_split(base64_encode($RSAPrivateKey), 64) .
'-----END RSA PRIVATE KEY-----';
}
src: https://raw.githubusercontent.com/phpseclib/phpseclib/master/phpseclib/Crypt/RSA.php
How to encrypt a JS generated RSA private key with a passphrase?
You have one of two choices. First, encrypt the entire key beofre it reaches disk. Then decrypt it before you use it. In this case, you treat the key like a file you want to encrypt.
Second, use PKCS #8, a.k.a. RFC 5208, Public-Key Cryptography Standards (PKCS) #8: Private-Key Information Syntax Specification Version 1.2. In particular, see section 6 of RFC 5208, EncryptedPrivateKeyInfo.
You have a third option, but its not advised. The third option is to use an encrypted PEM encoding. Its not advisable because its been superseded by PKCS #8.
In the future, you will have a fourth option, and that is to use WebCrypto to store your key. In this case, you moved the problem of secure storage to the platform.
Unfortunately, I don't know about the library you are using, so I don't know what it may (or may not offer). But the answers above cover the OpenSSL bits of your question.

File names with spaces returning signature mismatch error for Signed URLs in Google Cloud Storage

I'm using Google Cloud Storages Signed URLs feature with the below code. File names without spaces work fine, but those with spaces give this error:
The request signature we calculated does not match the signature you provided.
I'm passing the file name via ajax to the below code, which then returns the link to the user. I've tried a combination of rawurlencode, urlencode & urldecode but can't seem to work it out.
Thanks in advance
$bucket = 'savoy_storage';
if (isset($_GET['file']))
{
$file = urldecode($_GET['file']);
$link = storageURL( $bucket, $file);
echo $link;
}
//https://groups.google.com/forum/#!topic/google-api-php-client/kvN-yoyzf_w
function storageURL( $bucketName, $file) {
$expiry = time() + 3600;
$accessId = '1234#developer.gserviceaccount.com';
$stringPolicy = "GET\n\n\n".$expiry."\n/".$bucketName."/".$file;
$fp = fopen('key.pem', 'r');
$priv_key = fread($fp, 8192);
fclose($fp);
$pkeyid = openssl_get_privatekey($priv_key,"password");
if (openssl_sign( $stringPolicy, $signature, $pkeyid, 'sha256' )) {
$signature = urlencode( base64_encode( $signature ) );
return 'https://'.$bucketName.'.commondatastorage.googleapis.com/'.$file.'?
GoogleAccessId='.$accessId.'&Expires='.$expiry.'&Signature='.$signature;
}
}
If it helps here is the returned XML from the link:
<Error>
<Code>SignatureDoesNotMatch</Code>
<Message>
The request signature we calculated does not match the signature you provided. Check your Google secret key and signing method.
</Message>
<StringToSign>
GET 1391312980 /savoy_storage/August%20promotions.xlsx
</StringToSign>
</Error>

Cloudfront, HTML5 Video and Signed URLs

I am having trouble getting Cloudfront videos to play when using a signed url. If I do NOT require a signed URL, everything works fine. Here is the code that signs the url:
function rsa_sha1_sign($policy, $private_key_filename)
{
$signature = "";
// load the private key
$fp = fopen($private_key_filename, "r");
$priv_key = fread($fp, 8192);
fclose($fp);
//echo $priv_key;
$pkeyid = openssl_get_privatekey($priv_key);
// compute signature
openssl_sign($policy, $signature, $pkeyid);
// free the key from memory
openssl_free_key($pkeyid);
//echo $signature;
return $signature;
}
function url_safe_base64_encode($value)
{
$encoded = base64_encode($value);
// replace unsafe characters +, = and / with
// the safe characters -, _ and ~
return str_replace(
array('+', '=', '/'),
array('-', '_', '~'),
$encoded);
}
// No restriction
$keyPairId = "KEYPAIRID-DIST-NOT-REQUIRING-SIGNEDURL";
$download_url = "http://URL-DIST-NOT-REQUIRING-SIGNEDURL.cloudfront.net/myvideo.mp4";
//This is just a flag to aid in switching between the 2 testing distributions
if($restrict) {
$key_pair_id = "KEYPAIRID-DIST-REQUIRING-SIGNEDURL"";
$download_url = "http://URL-DIST-REQUIRING-SIGNEDURL.cloudfront.net/myvideo.mp4";
}
$DateLessThan = time() + (24*7*60*60);
$policy = '{"Statement":[{"Resource":"'.$download_url.'","Condition":{"DateLessThan":{"AWS:EpochTime":'.$DateLessThan.'}}}]}';
$private_key_file = "/path/to/privatekey.pem";
$signature = rsa_sha1_sign($policy, $private_key_file);
$signature = url_safe_base64_encode($signature);
$final_url = $download_url.'?Policy='.url_safe_base64_encode($policy).'&Signature='.$signature.'&Key-Pair-Id='.$key_pair_id;
echo $final_url;
In the above, if I use the Cloudfront distribution that requires a signed URL (by passing in $restrict=1) then I get an error, "Video not found". In console I see that the GET request for the video was canceled (Status Text: cancelled... weirdly I see this twice). If I use the Distribution that doe NOT require a signed URL everything works fine and the video loads correctly.
What am I missing? The distributions are identical except for the requiring of the signed URL and they both use the same Amazon S3 bucket source for the video.
The player is flowplayer(HTML5) but since it works fine without the signed url I would assume the player isn't the problem.
Please see my answer here: Amazon S3 signed url not working with flowplayer
Hopefully that will help.
In my case, I needed to remove the "mp4:" prefix before signing the url, and then add it back on again.

Signature verification of in-app billing v3 in PHP server with openssl_verify

I have seen many posts with this issue and tested many possible solutions but the verification was always false.
So, I am passing both of this parametters via POST to the PHP server:
String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA");<br>
String dataSignature = data.getStringExtra("INAPP_DATA_SIGNATURE");
This is the method I use:
$result = openssl_verify($responseData, base64_decode($signature),
key, OPENSSL_ALGO_SHA1);
I used the method json_decode to verify that purchaseData is correct.
I also use base64_decode for the dataSignature.
And this is how I form my key with the public key that I have with my Google Publisher account:
const KEY_PREFIX = "-----BEGIN PUBLIC KEY-----\n";<br>
const KEY_SUFFIX = '-----END PUBLIC KEY-----';
$key = self::KEY_PREFIX . chunk_split($publicKey, 64, "\n") . self::KEY_SUFFIX;
The tests are performed with an account test of a uploaded game in Google Play, so the In App Purchases are real but with no charges.
What I am missing here?
You have formatted the key but you are missing one step where you need to extract the public key from the PEM.
Here are the steps:
$key = self::KEY_PREFIX . chunk_split($publicKey, 64, "\n") . self::KEY_SUFFIX;
$key = openssl_get_publickey($key);
$result = openssl_verify($responseData, base64_decode($signature), $key, OPENSSL_ALGO_SHA1);

Categories