תהליך חתימה V4 באמצעות תוכנית משלכם

המאמר הזה מתאר אלגוריתם להטמעת תהליך חתימת V4, שמאפשר יצירה של כתובות URL חתומות של מפתח RSA ב-Cloud Storage בתהליך העבודה שלכם, באמצעות שפת תכנות לבחירתכם.

לפני שמתחילים

לפני יצירת תוכנית שמממשת את תהליך חתימת V4, אתם צריכים לבצע את השלבים הבאים:

  1. יצירה של חשבון שירות. אם כבר יש לכם חשבון שירות, אתם יכולים לדלג על השלב הזה. מידע נוסף על חשבונות שירות זמין במאמר סקירה כללית על חשבונות שירות.

  2. מתן הרשאה מספקת לחשבון השירות כדי שהוא יוכל לבצע את הבקשה שנשלחת מכתובת ה-URL החתומה.

    לדוגמה, אם כתובת ה-URL החתומה מאפשרת למשתמש לקרוא נתוני אובייקטים, לחשבון השירות צריכה להיות הרשאה לקרוא את נתוני האובייקטים.

  3. אם אלגוריתם החתימה שבו מתכוונים להשתמש לא מובנה ב-Cloud de Confiance, צריך ליצור מפתח פרטי חדש או להשתמש במפתח פרטי שקיים בחשבון השירות. המפתח יכול להיות בפורמט JSON או PKCS12.

אלגוריתם לחתימה על כתובות URL

התוכנית צריכה לכלול את השלבים הבאים:

  1. יצירה של הבקשה הקנונית כמחרוזת. בבקשה הקנונית מוגדרים רכיבים שהמשתמשים צריכים לכלול בבקשה כשהם משתמשים בכתובת ה-URL החתומה.

    מידע על החלקים והפורמט הנדרשים מופיע בקטע בקשות קנוניות.

  2. בנייה של המחרוזת לחתימה. המחרוזת לחתימה היא הבסיס ליצירת החתימה, וכוללת בתוכה את ערך הגיבוב (hash) המקודד בקידוד ההקסדצימלי של הבקשה הקנונית.

    מידע על הפורמט של מחרוזת לחתימה מופיע בקטע חתימות.

  3. חתימה על המחרוזת לחתימה באמצעות חתימת RSA עם SHA-256. התוצאה של החתימה הזו היא חתימת הבקשה. אפשר לחתום במספר דרכים:

  4. בונים את כתובת ה-URL החתומה באמצעות שרשור המחרוזות הבא:

    HOSTNAME + PATH_TO_RESOURCE + "?" + CANONICAL_QUERY_STRING + "&X-Goog-Signature=" + REQUEST_SIGNATURE

    כתובת ה-URL החתומה כוללת את הרכיבים הבאים:

    • HOSTNAME: צריך להיות https://storage.s3nsapis.fr.

    • PATH_TO_RESOURCE: צריך להתאים לערך שבו השתמשתם לבניית הבקשה הקנונית.

    • CANONICAL_QUERY_STRING: צריך להתאים לערכים שבהם השתמשתם לבניית הבקשה הקנונית.

    • REQUEST_SIGNATURE: זהו הפלט כתוצאה מהשימוש בחתימה של RSA בשלב הקודם, בקידוד הקסדצימלי.

    הנה דוגמה לכתובת URL שהושלמה:

    https://storage.s3nsapis.fr/example-bucket/cat.jpeg?X-Goog-Algorithm=GOOG4-
    RSA-SHA256&X-Goog-Credential=example%40example-project.s3ns.iam.gserviceaccount.com
    %2F20181026%2Fus%2Fstorage%2Fgoog4_request&X-Goog-Date=20181026T211942Z&X-Goog
    -expires=3600&X-Goog-Signedheaders=host&X-Goog-Signature=2d2a6f5055eb004b8690b
    9479883292ae7450cdc15f17d7f99bc49b916f9e7429106ed7e5858ae6b4ab0bbbdb1a8ccc364d
    ad3a0da2caebd30887a70c5b2569d089ceb8afbde3eed4dff5086f0db5483998c175980991fe89
    9fbd2cd8cb813b00165e8d56e0a8aa7b3d7a12ee1baa8400611040f05b50a1a8eab5ba223fe137
    5747748de950ec7a4dc50f8382a6ffd4994ac42498d7daa703d9a414d4475154d0e7edaa92d4f2
    507d92c1f7e8efa7cab64df68b5df48575b9259d8d0bdb5dc752bdf07bd162d98ff2924f2e4a26
    fa6b3cede73ad5333c47d146a21c2ab2d97115986a12c68ff37346d6c2ca83e56b8ec8ad956327
    10b489b75c35697d781c38e

תוכנת Python לדוגמה

אפשר להשתמש בספריות הלקוח של Cloud Storage כדי ליצור כתובות URL חתומות לשפות תכנות נפוצות רבות. לדוגמאות, ראו תהליך חתימת V4 עם כלים של Cloud Storage.

בדוגמה הבאה מוצג מימוש של האלגוריתם לחתימה על כתובות URL ללא שימוש בספריות הלקוח של Cloud Storage. הדוגמה מבוססת על שפת התכנות Python, אבל אפשר להתאים אותה לשפות אחרות.

import binascii
import collections
import datetime
import hashlib
import sys
from urllib.parse import quote

# pip install google-auth
from google.oauth2 import service_account

# pip install six
import six


def generate_signed_url(
    service_account_file,
    bucket_name,
    object_name,
    subresource=None,
    expiration=604800,
    http_method="GET",
    query_parameters=None,
    headers=None,
):
    if expiration > 604800:
        print("Expiration Time can't be longer than 604800 seconds (7 days).")
        sys.exit(1)

    escaped_object_name = quote(six.ensure_binary(object_name), safe=b"/~")
    canonical_uri = f"/{escaped_object_name}"

    datetime_now = datetime.datetime.now(tz=datetime.timezone.utc)
    request_timestamp = datetime_now.strftime("%Y%m%dT%H%M%SZ")
    datestamp = datetime_now.strftime("%Y%m%d")

    google_credentials = service_account.Credentials.from_service_account_file(
        service_account_file
    )
    client_email = google_credentials.service_account_email
    credential_scope = f"{datestamp}/auto/storage/goog4_request"
    credential = f"{client_email}/{credential_scope}"

    if headers is None:
        headers = dict()
    host = f"{bucket_name}.storage.googleapis.com"
    headers["host"] = host

    canonical_headers = ""
    ordered_headers = collections.OrderedDict(sorted(headers.items()))
    for k, v in ordered_headers.items():
        lower_k = str(k).lower()
        strip_v = str(v).lower()
        canonical_headers += f"{lower_k}:{strip_v}\n"

    signed_headers = ""
    for k, _ in ordered_headers.items():
        lower_k = str(k).lower()
        signed_headers += f"{lower_k};"
    signed_headers = signed_headers[:-1]  # remove trailing ';'

    if query_parameters is None:
        query_parameters = dict()
    query_parameters["X-Goog-Algorithm"] = "GOOG4-RSA-SHA256"
    query_parameters["X-Goog-Credential"] = credential
    query_parameters["X-Goog-Date"] = request_timestamp
    query_parameters["X-Goog-Expires"] = expiration
    query_parameters["X-Goog-SignedHeaders"] = signed_headers
    if subresource:
        query_parameters[subresource] = ""

    canonical_query_string = ""
    ordered_query_parameters = collections.OrderedDict(sorted(query_parameters.items()))
    for k, v in ordered_query_parameters.items():
        encoded_k = quote(str(k), safe="")
        encoded_v = quote(str(v), safe="")
        canonical_query_string += f"{encoded_k}={encoded_v}&"
    canonical_query_string = canonical_query_string[:-1]  # remove trailing '&'

    canonical_request = "\n".join(
        [
            http_method,
            canonical_uri,
            canonical_query_string,
            canonical_headers,
            signed_headers,
            "UNSIGNED-PAYLOAD",
        ]
    )

    canonical_request_hash = hashlib.sha256(canonical_request.encode()).hexdigest()

    string_to_sign = "\n".join(
        [
            "GOOG4-RSA-SHA256",
            request_timestamp,
            credential_scope,
            canonical_request_hash,
        ]
    )

    # signer.sign() signs using RSA-SHA256 with PKCS1v15 padding
    signature = binascii.hexlify(
        google_credentials.signer.sign(string_to_sign)
    ).decode()

    scheme_and_host = "{}://{}".format("https", host)
    signed_url = "{}{}?{}&x-goog-signature={}".format(
        scheme_and_host, canonical_uri, canonical_query_string, signature
    )

    return signed_url

המאמרים הבאים