המדריך למפתחים של 'גישה מאומתת של Chrome'

מידע על המדריך

ה-Chrome Verified Access API מאפשר לשירותי רשת, כמו רשתות VPN, דפי אינטראנט וכן הלאה, לאמת באופן קריפטוגרפי שהלקוחות שלהם אמיתיים ומצייתים למדיניות הארגון. רוב הארגונים הגדולים נדרשים לאפשר רק למכשירים בניהול הארגון להתחבר לרשתות WPA2 EAP-TLS שלהם, גישה ברמה גבוהה יותר לרשתות VPN ודפי אינטראנט עם TLS הדדי. הרבה פתרונות קיימים מסתמכים על בדיקות היוריסטיות לגבי אותו לקוח שייתכן שנפגע. כאן מוצג האתגר שלפיו ייתכן שהאותות שמסתמכים על כך כדי לאמת את הסטטוס הלגיטימי של המכשיר עלולים לזייף. במדריך הזה מפורטות התחייבויות קריפטוגרפיות בגיבוי חומרה של זהות המכשיר, שהמצב שלו לא השתנה והמצב שלו תואם למדיניות בזמן האתחול. השירות נקרא Verified Access.

הקהל הראשי מנהלי דומיינים של IT ארגוני
רכיבים טכניים ChromeOS, Google Verified Access API

דרישות מוקדמות לגישה מאומתת

צריך להשלים את ההגדרה הבאה לפני שמיישמים את תהליך הגישה המאומתת.

מפעילים את ה-API

מגדירים פרויקט במסוף Google API ומפעילים את ממשק ה-API:

  1. במסוף Google API אפשר ליצור פרויקט קיים או להשתמש בו.
  2. נכנסים לדף Enabled APIs & services.
  3. מפעילים את Chrome Verified Access API.
  4. כדי ליצור מפתח API לאפליקציה, פועלים לפי מסמכי התיעוד של Google Cloud API.

יצירה של חשבון שירות

כדי ששירות הרשת יקבל גישה ל-Chrome Verified Access API כדי לאמת את תגובת האתגר, עליכם ליצור חשבון שירות ומפתח לחשבון שירות (לא צריך ליצור פרויקט חדש ב-Cloud, אפשר להשתמש באותו פרויקט).

אחרי שתיצרו את המפתח של חשבון השירות, תוכלו להוריד קובץ מפתח פרטי של חשבון שירות. זהו העותק היחיד של המפתח הפרטי, לכן חשוב להקפיד לאחסן אותו באופן מאובטח.

רישום של מכשיר Chromebook מנוהל

כדי שתהיה לכם גישה מאומתת, אתם צריכים להגדיר במכשיר Chromebook שמנוהל כראוי באמצעות התוסף ל-Chrome.

  1. מכשיר ה-Chromebook חייב להיות רשום לניהול ארגוני או חינוכי.
  2. המשתמש במכשיר חייב להיות משתמש רשום מאותו דומיין.
  3. צריך להתקין במכשיר את התוסף של Chrome לגישה מאומתת.
  4. כללי המדיניות מוגדרים להפעיל את הגישה המאומתת, להוסיף את התוסף ל-Chrome לרשימת ההיתרים ולהעניק גישה ל-API לחשבון השירות שמייצג את שירות הרשת (פרטים נוספים זמינים במסמכי העזרה במסוף Google Admin).

אימות המשתמש והמכשיר

מפתחים יכולים להשתמש בגישה מאומתת לצורך אימות משתמשים או מכשירים, או להשתמש בשתיהן כדי להגביר את האבטחה:

  • אימות המכשיר – כשאימות המכשיר בוצע בהצלחה, מובטחת שמכשיר ה-Chrome רשום בדומיין מנוהל ושהוא תואם למדיניות המכשיר במצב אתחול מאומת, כפי שציין מנהל הדומיין. אם לשירות הרשת ניתנה הרשאה לראות את זהות המכשיר (עיינו במסמכי העזרה של מסוף Google Admin), הוא גם מקבל מזהה מכשיר שיכול לשמש לביקורת, למעקב או לקריאה ל-Directory API.

  • אימות משתמשים – אימות המשתמשים מבטיח שמשתמש Chrome שמחובר הוא משתמש מנוהל, משתמש במכשיר רשום ותואם למדיניות המשתמש במצב הפעלה מאומתת, כפי שקבע מנהל הדומיין. אם לשירות הרשת ניתנה הרשאה לקבל נתוני משתמש נוספים, הוא יקבל גם בקשת חתימה על אישור שהונפק על ידי המשתמש (נציג שירות לקוחות בצורה של מפתח ציבורי ואתגר חתום, או SPKAC, שידוע גם כפורמט ליצירת מפתחות).

איך לאמת את המשתמש והמכשיר

  1. קבלת אתגר – התוסף ל-Chrome במכשיר יוצר קשר עם Verified Access API כדי לקבל אתגר. האתגר הוא מבנה נתונים אטום (blob בחתימה של Google) שאפשר להשתמש בו למשך דקה. כלומר, אימות אתגר התגובה (שלב 3) ייכשל אם משתמשים באתגר לא עדכני.

    במקרה השימוש הפשוט ביותר, המשתמש מפעיל את התהליך באמצעות לחיצה על לחצן שהתוסף יוצר (זהו גם התוסף לדוגמה ש-Google מספקת).

    var apiKey = 'YOUR_API_KEY_HERE';
    var challengeUrlString =
      'https://verifiedaccess.googleapis.com/v2/challenge:generate?key=' + apiKey;
    
    // Request challenge from URL
    var xmlhttp = new XMLHttpRequest();
    xmlhttp.open('POST', challengeUrlString, true);
    xmlhttp.send();
    xmlhttp.onreadystatechange = function() {
      if (xmlhttp.readyState == 4) {
        var challenge = xmlhttp.responseText;
        console.log('challenge: ' + challenge);
        // v2 of the API returns an encoded challenge so no further challenge processing is needed
      }
    };
    

    אתגר עזרה לקידוד – אם אתם משתמשים בגרסה 1 של ה-API, צריך לקודד את האתגר.

    // This can be replaced by using a third-party library such as
    // [https://github.com/dcodeIO/ProtoBuf.js/wiki](https://github.com/dcodeIO/ProtoBuf.js/wiki)
    /**
     * encodeChallenge convert JSON challenge into base64 encoded byte array
     * @param {string} challenge JSON encoded challenge protobuf
     * @return {string} base64 encoded challenge protobuf
     */
    var encodeChallenge = function(challenge) {
      var jsonChallenge = JSON.parse(challenge);
      var challengeData = jsonChallenge.challenge.data;
      var challengeSignature = jsonChallenge.challenge.signature;
    
      var protobufBinary = protoEncodeChallenge(
          window.atob(challengeData), window.atob(challengeSignature));
    
      return window.btoa(protobufBinary);
    };
    
    /**
     * protoEncodeChallenge produce binary encoding of the challenge protobuf
     * @param {string} dataBinary binary data field
     * @param {string} signatureBinary binary signature field
     * @return {string} binary encoded challenge protobuf
     */
    var protoEncodeChallenge = function(dataBinary, signatureBinary) {
      var protoEncoded = '';
    
      // See https://developers.google.com/protocol-buffers/docs/encoding
      // for encoding details.
    
      // 0x0A (00001 010, field number 1, wire type 2 [length-delimited])
      protoEncoded += '\u000A';
    
      // encode length of the data
      protoEncoded += varintEncode(dataBinary.length);
      // add data
      protoEncoded += dataBinary;
    
      // 0x12 (00010 010, field number 2, wire type 2 [length-delimited])
      protoEncoded += '\u0012';
      // encode length of the signature
      protoEncoded += varintEncode(signatureBinary.length);
      // add signature
      protoEncoded += signatureBinary;
    
      return protoEncoded;
    };
    
    /**
     * varintEncode produce binary encoding of the integer number
     * @param {number} number integer number
     * @return {string} binary varint-encoded number
     */
    var varintEncode = function(number) {
      // This only works correctly for numbers 0 through 16383 (0x3FFF)
      if (number <= 127) {
        return String.fromCharCode(number);
      } else {
        return String.fromCharCode(128 + (number & 0x7f), number >>> 7);
      }
    };
    
  2. יצירת תגובה לאתגר – התוסף ל-Chrome משתמש באתגר שקיבל בשלב 1 כדי לקרוא ל-enterprise.platformKeys Chrome API. הפעולה הזו יוצרת תגובה לאתגר חתום ומוצפנת, שהתוסף כולל בבקשת הגישה שהוא שולח לשירות הרשת.

    בשלב הזה אין ניסיון להגדיר פרוטוקול שמשמש לתקשורת באמצעות התוסף ושירות הרשת. שתי הישויות האלה מוטמעות על ידי מפתחים חיצוניים, ולא נקבע איך הם מתקשרים זה עם זה. דוגמה לשליחת אתגר (בקידוד כתובת URL) כפרמטר של מחרוזת שאילתה, באמצעות HTTP POST או באמצעות כותרת HTTP מיוחדת.

    הנה קוד לדוגמה כדי ליצור תשובה לאתגר:

    יצירת תשובה לאתגר

      // Generate challenge response
      var encodedChallenge; // obtained by generate challenge API call
      try {
        if (isDeviceVerification) { // isDeviceVerification set by external logic
          chrome.enterprise.platformKeys.challengeKey(
              {
                scope: 'MACHINE',
                challenge: decodestr2ab(encodedChallenge),
              },
              ChallengeCallback);
        } else {
          chrome.enterprise.platformKeys.challengeKey(
              {
                scope: 'USER',
                challenge: decodestr2ab(encodedChallenge),
                registerKey: { 'RSA' }, // can also specify 'ECDSA'
              },
              ChallengeCallback);
        }
      } catch (error) {
        console.log('ERROR: ' + error);
      }
    

    אתגר את פונקציית הקריאה החוזרת

      var ChallengeCallback = function(response) {
        if (chrome.runtime.lastError) {
          console.log(chrome.runtime.lastError.message);
        } else {
          var responseAsString = ab2base64str(response);
          console.log('resp: ' + responseAsString);
        ... // send on to network service
       };
      }
    

    קוד עזרה להמרה של ArrayBuffer

      /**
       * ab2base64str convert an ArrayBuffer to base64 string
       * @param {ArrayBuffer} buf ArrayBuffer instance
       * @return {string} base64 encoded string representation
       * of the ArrayBuffer
       */
      var ab2base64str = function(buf) {
        var binary = '';
        var bytes = new Uint8Array(buf);
        var len = bytes.byteLength;
        for (var i = 0; i < len; i++) {
          binary += String.fromCharCode(bytes[i]);
        }
        return window.btoa(binary);
      }
    
      /**
       * decodestr2ab convert a base64 encoded string to ArrayBuffer
       * @param {string} str string instance
       * @return {ArrayBuffer} ArrayBuffer representation of the string
       */
      var decodestr2ab = function(str) {
        var binary_string =  window.atob(str);
        var len = binary_string.length;
        var bytes = new Uint8Array(len);
        for (var i = 0; i < len; i++)        {
            bytes[i] = binary_string.charCodeAt(i);
        }
        return bytes.buffer;
      }
    
  3. אימות התגובה לאתגר – עם קבלת התשובה באתגר ממכשיר (למשל, כתוסף לפרוטוקול אימות קיים), שירות הרשת צריך להפעיל את Verified Access API כדי לאמת את זהות המכשיר ואת מצב המדיניות (ראו קוד לדוגמה בהמשך). כדי להילחם בזיופים, אנחנו ממליצים ששירות הרשת יזהה את הלקוח שאליו הוא משוחח ולכלול בבקשה את הזהות הצפויה של הלקוח:

    • לאימות המכשיר, יש לציין את דומיין המכשיר הצפוי . במקרים רבים, סביר להניח שזה ערך בתוך הקוד, כי שירות הרשת מגן על המשאבים בדומיין מסוים. אם המצב הזה לא היה ידוע מזמן, אפשר להסיק זאת על סמך זהות המשתמש.
    • לאימות משתמשים, צריך לציין את כתובת האימייל של המשתמש. אנחנו מצפים ששירות הרשת יכיר את המשתמשים שלו (בדרך כלל המשתמשים יצטרכו להיכנס לחשבון).

    כשמפעילים את Google API, הוא מבצע מספר בדיקות, למשל:

    • יש לוודא שהתגובה לאתגר נוצרה על ידי ChromeOS ושלא מתבצע בה שינוי בזמן ההעברה
    • אימות שהמכשיר או המשתמש מנוהלים על ידי ארגון.
    • צריך לוודא שזהות המכשיר או המשתמש תואמים לזהות הצפויה (אם האדם הזה סופק).
    • יש לוודא שהאתגר שמשיבים לו חדש (לא לפני יותר מדקה).
    • יש לוודא שהמכשיר או המשתמש מצייתים למדיניות כפי שצוינה מנהל הדומיין.
    • מוודאים שמבצע הקריאה החוזרת (שירות הרשת) קיבל הרשאה לבצע קריאה ל-API.
    • אם המתקשר מקבל הרשאה לקבל נתונים נוספים על המכשיר או המשתמש, צריך לכלול בתגובה את מזהה המכשיר או את בקשת חתימת האישור (CSR) של המשתמש.

    בדוגמה הזו נעשה שימוש בספריית gRPC

    import com.google.auth.oauth2.GoogleCredentials;
    import com.google.auth.oauth2.ServiceAccountCredentials;
    import com.google.chrome.verifiedaccess.v2.VerifiedAccessGrpc;
    import com.google.chrome.verifiedaccess.v2.VerifyChallengeResponseRequest;
    import com.google.chrome.verifiedaccess.v2.VerifyChallengeResponseResult;
    import com.google.protobuf.ByteString;
    
    import io.grpc.ClientInterceptor;
    import io.grpc.ClientInterceptors;
    import io.grpc.ManagedChannel;
    import io.grpc.auth.ClientAuthInterceptor;
    import io.grpc.netty.GrpcSslContexts;
    import io.grpc.netty.NettyChannelBuilder;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.util.Arrays;
    import java.util.concurrent.Executors;
    
    // https://cloud.google.com/storage/docs/authentication#generating-a-private-key
    private final String clientSecretFile = "PATH_TO_GENERATED_JSON_SECRET_FILE";
    
    private ManagedChannel channel;
    private VerifiedAccessGrpc.VerifiedAccessBlockingStub client;
    
    void setup() {
    
       channel = NettyChannelBuilder.forAddress("verifiedaccess.googleapis.com", 443)
          .sslContext(GrpcSslContexts.forClient().ciphers(null).build())
          .build();
    
       List<ClientInterceptor> interceptors = Lists.newArrayList();
       // Attach a credential for my service account and scope it for the API.
       GoogleCredentials credentials =
           ServiceAccountCredentials.class.cast(
               GoogleCredentials.fromStream(
                   new FileInputStream(new File(clientSecretFile))));
      credentials = credentials.createScoped(
          Arrays.<String>asList("https://www.googleapis.com/auth/verifiedaccess"));
      interceptors.add(
           new ClientAuthInterceptor(credentials, Executors.newSingleThreadExecutor()));
    
      // Create a stub bound to the channel with the interceptors applied
      client = VerifiedAccessGrpc.newBlockingStub(
          ClientInterceptors.intercept(channel, interceptors));
    }
    
    /**
     * Invokes the synchronous RPC call that verifies the device response.
     * Returns the result protobuf as a string.
     *
     * @param signedData base64 encoded signedData blob (this is a response from device)
     * @param expectedIdentity expected identity (domain name or user email)
     * @return the verification result protobuf as string
     */
    public String verifyChallengeResponse(String signedData, String expectedIdentity)
      throws IOException, io.grpc.StatusRuntimeException {
      VerifyChallengeResponseResult result =
        client.verifyChallengeResponse(newVerificationRequest(signedData,
            expectedIdentity)); // will throw StatusRuntimeException on error.
    
      return result.toString();
    }
    
    private VerifyChallengeResponseRequest newVerificationRequest(
      String signedData, String expectedIdentity) throws IOException {
      return VerifyChallengeResponseRequest.newBuilder()
        .setChallengeResponse(
            ByteString.copyFrom(BaseEncoding.base64().decode(signedData)))
        .setExpectedIdentity(expectedIdentity == null ? "" : expectedIdentity)
        .build();
    }
    
  4. הענקת גישה – השלב הזה ספציפי גם לשירות ברשת. זאת הצעה להטמעה (לא לפי המלצה). הפעולות האפשריות הן:

    • יצירת קובץ cookie של סשן
    • הנפקת אישור למשתמש או למכשיר. במקרה שאימות המשתמש בוצע בהצלחה, ובהנחה שלשירות הרשת ניתנה גישה לנתוני משתמש נוספים (באמצעות המדיניות של מסוף Google Admin), הוא יקבל הודעת CSR בחתימת משתמש, שאפשר יהיה להשתמש בו כדי לקבל את האישור בפועל מרשות האישורים. בשילוב עם Microsoft CA, שירות הרשת יכול לשמש כמתווך ולהשתמש בממשק ICertRequest.

שימוש באישורי לקוח עם 'גישה מאומתת'

שימוש באישורי לקוח עם 'גישה מאומתת'.

בארגון גדול יכולים להיות כמה שירותי רשת (שרתי VPN, נקודות גישה ל-Wi-Fi, חומות אש ואתרי אינטראנט מרובים) שכדאי להם להפעיל את Verified Access. עם זאת, ייתכן שלא יהיה פרקטי לבניית הלוגיקה של שלבים 2 עד 4 (בקטע שלמעלה) בכל אחד משירותי הרשת האלה. לעיתים קרובות, לרבים משירותי הרשת האלה כבר יש יכולת לדרוש אישורי לקוח כחלק מהאימותים שלהם (לדוגמה, EAP-TLS או דפי אינטראנט TLS שונים). לכן, אם רשות האישורים הארגונית שמנפיקה את אישורי הלקוח האלה יכולה ליישם את שלבים 2 עד 4 ולהתנות את הנפקת אישור הלקוח באימות האתגר-תשובה, החזקת האישור יכולה להיות הוכחה לכך שהלקוח אמיתי ומציית למדיניות החברה. לאחר מכן, כל נקודת גישה ל-Wi-Fi, שרת VPN וכו' יוכלו לבדוק את אישור הלקוח במקום לבצע את השלבים 2 עד 4.

כלומר, כאן רשות האישורים (המנפיקה את אישור הלקוח למכשירים ארגוניים) ממלאת את תפקיד שירות הרשת באיור 1. צריך להפעיל את Verified Access API, ולספק את האישור ללקוח רק אחרי שהאימות של תגובת האתגר יסתיים. מתן האישור ללקוח זהה לשלב 4 – הענקת גישה, שמתוארת באיור 1.

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