Erişim Denetimi

Tink'in amaçlarından biri, kötü uygulamalardan uzaklaşmaktır. Bu bölümde özellikle önemli olan iki nokta var:

  1. Tink, kullanıcıların gizli anahtar materyaline erişemeyeceği şekilde kullanılmasını teşvik eder. Bunun yerine, gizli anahtarlar, Tink'in bu tür sistemleri desteklediği önceden tanımlanmış yöntemlerden biri kullanılarak mümkün olduğunda bir KMS'de depolanmalıdır.
  2. Tink, kullanıcıları anahtarların parçalarına erişmekten vazgeçirir çünkü bu şekilde çoğu zaman uyumluluk hataları ortaya çıkar.

Elbette, pratikte bu ilkelerden her ikisinin de zaman zaman ihlal edilmesi gerekir. Tink, bunun için farklı mekanizmalar sunar.

Gizli Anahtar Erişim Jetonları

Gizli anahtar materyaline erişmek için kullanıcıların, jetona sahip olmaları gerekir (bu genellikle herhangi bir işlev olmadan, genellikle yalnızca bir sınıfın bir nesnesidir). Jeton, genellikle InsecureSecretKeyAccess.get() gibi bir yöntemle sağlanır. Google'da kullanıcıların Bazel DERLEME görünürlüğü kullanılarak bu işlevi kullanması engellenir. Güvenlik incelemeciler, bu işlevin kullanımları için Google dışında kod tabanlarında arama yapabilirler.

Bu jetonların yararlı bir özelliği, devredilebilir olmalarıdır. Örneğin, rastgele bir Tink tuşunu serileştiren bir işlevinizin olduğunu varsayalım:

String serializeKey(Key key, @Nullable SecretKeyAccess secretKeyAccess);

Gizli anahtar materyali olan anahtarlar için bu işlev, secretKeyAccess nesnesinin boş olmamasını ve gerçek bir SecretKeyAccess jetonunun depolanmasını gerektirir. Gizli materyal içermeyen anahtarlar için secretKeyAccess yoksayılır.

Böyle bir işlev göz önüne alındığında, tüm anahtar kümesini serileştiren bir işlev yazmak mümkündür: String serializeKeyset(KeysetHandle keyset, @Nullable SecretKeyAccess gizliKeyAccess);

Bu işlev, dahili olarak anahtar kümesindeki her anahtar için serializeKey çağrısı yapar ve verilen secretKeyAccess değerini temel işleve geçirir. Daha sonra, gizli anahtar materyalini seri hale getirmeye gerek kalmadan serializeKeyset yöntemini çağıran kullanıcılar, ikinci bağımsız değişken olarak null kullanabilir. Gizli anahtar materyalini seri hale getirmesi gereken kullanıcıların InsecureSecretKeyAccess.get() kullanması gerekir.

Anahtarın bazı bölümlerine erişim

"Anahtarı yeniden kullanma saldırısı" nispeten yaygın bir güvenlik hatasıdır. Bu durum, kullanıcılar bir RSA anahtarının n modülünü ve örneğin d ile e üslerini iki farklı ayarda (ör. imzaları ve şifrelemeleri hesaplamak için) yeniden kullandığında ortaya çıkabilir1.

Şifreleme anahtarlarıyla çalışırken nispeten yaygın olarak yapılan bir başka hata da anahtarın bir bölümünü belirtip meta verileri "varsaymak"tır. Örneğin, bir kullanıcının farklı bir kitaplıkla kullanmak üzere Tink'teki bir RSASSA-PSS ortak anahtarını dışa aktarmak istediğini varsayalım. Tink'te bu anahtarlar aşağıdaki parçalara sahiptir:

  • n modülü
  • Genel üs e
  • Dahili olarak kullanılan iki karma işlevinin spesifikasyonu
  • Algoritmada dahili olarak kullanılan takviye değerin uzunluğu.

Bu tür bir anahtarı dışa aktarırken karma işlevlerini ve takviye uzunluğunu yoksayabilirsiniz. Diğer kitaplıklar karma fonksiyonlarını istemediği (örneğin, SHA256'nın kullanıldığını varsaymadığı) ve Tink'te kullanılan karma işlevi tesadüfen diğer kitaplıktakiyle aynı olduğu (veya karma işlevleri özel olarak diğer kitaplıkla birlikte çalışacak şekilde seçilmiştir) çoğu zaman bu şekilde işe yarar.

Yine de karma işlevlerinin göz ardı edilmesi pahalı bir hata olabilir. Bunu görmek için daha sonra Tink anahtar kümesine farklı bir karma işlevine sahip yeni bir anahtarın eklendiğini varsayalım. Daha sonra anahtarın bu yöntemle dışa aktarıldığını ve bu anahtarı diğer kitaplıkla birlikte kullanan bir iş ortağına verildiğini varsayalım. Tink artık farklı bir dahili karma işlevi olduğunu varsayarak imzayı doğrulayamaz.

Bu durumda, karma işlevi diğer kitaplığın beklediğiyle eşleşmiyorsa anahtarı dışa aktaran işlev başarısız olur. Aksi takdirde, dışa aktarılan anahtar uyumsuz şifreli metinler veya imzalar oluşturduğundan bir işe yaramaz.

Tink, bu tür hataları önlemek için yalnızca kısmi olan ancak tam anahtar olarak karıştırılabilen anahtar materyaline erişim sağlayan işlevleri kısıtlar. Örneğin, Java'da Tink bunun için RestrictedApi'yi kullanır.

Bu tür bir ek açıklama kullanan kullanıcılar, hem anahtar yeniden kullanım saldırılarını hem de uyumsuzlukları önlemekten sorumludur.

En İyi Uygulama: Tink nesnelerini anahtar içe aktarmada mümkün olduğunca erken kullanın

Anahtarları Tink'ten dışa aktarırken veya anahtarları Tink'e aktarırken "kısmi anahtar erişimi" ile kısıtlanmış yöntemlerle sık sık karşılaşırsınız.

Tink Anahtarı nesnesi doğru algoritmayı tam olarak belirttiği ve tüm meta verileri anahtar materyaliyle birlikte depoladığı için bu, anahtar karışıklığı saldırısı riskini en aza indirir.

Aşağıdaki örneği inceleyin:

Yazılmamış kullanım:

void verifyEcdsaSignature(ECPoint ecPoint, byte[] signature, byte[] message)
        throws Exception {
    EcdsaParameters parameters =
        EcdsaParameters.builder()
            .setSignatureEncoding(EcdsaParameters.SignatureEncoding.IEEE_P1363)
            .setCurveType(EcdsaParameters.CurveType.NIST_P256)
            .setHashType(EcdsaParameters.HashType.SHA256)
            .setVariant(EcdsaParameters.Variant.NO_PREFIX)
            .build();
    EcdsaPublicKey key =
        EcdsaPublicKey.builder()
            .setParameters(parameters)
            .setPublicPoint(ecPoint)
            .build();
    KeysetHandle handle = KeysetHandle.newBuilder()
       .addEntry(KeysetHandle.importKey(key).withFixedId(1).makePrimary())
       .build();
    PublicKeyVerify publicKeyVerify = handle.getPrimitive(PublicKeyVerify.class);
    publicKeyVerify.verify(signature, message);
}

Bu durum hataya açıktır: Çağrı sitesinde, aynı ecPoint öğesini başka bir algoritmayla asla kullanmamanız çok kolaydır. Örneğin, encryptWithECHybridEncrypt adlı benzer bir işlev varsa çağrıyı yapan, bir mesajı şifrelemek için aynı eğri noktasını kullanabilir. Bu da kolayca güvenlik açıklarına yol açabilir.

Bunun yerine, verifyEcdsaSignature değişkenini ilk bağımsız değişkenin EcdsaPublicKey olacağı şekilde değiştirmek daha iyidir. Aslında, anahtar diskten veya ağdan her okunduğunda hemen bir EcdsaPublicKey nesnesine dönüştürülmelidir. Bu noktada anahtarın nasıl kullanıldığını zaten bildiğiniz için bu anahtarı kullanmak en doğrusudur.

Önceki kod daha da iyileştirilebilir. EcdsaPublicKey içinde geçmek yerine KeysetHandle içinde geçmek daha iyidir. Başka bir işlem yapmadan kodu anahtar rotasyonu için hazırlar. Bu tercih edilmeli.

Bununla birlikte, iyileştirmeler yapılmamıştır. Ancak, PublicKeyVerify nesnesini geçirmek daha iyidir: Bu işlev, bu işlev için yeterlidir. Dolayısıyla PublicKeyVerify nesnesinin geçirilmesi, bu işlevin kullanılabileceği yerleri potansiyel olarak artırır. Ancak bu noktada, fonksiyon önemsiz hale gelir ve satır içine alınabilir.

Öneri: Anahtar materyali diskten veya ağdan ilk kez okunduğunda karşılık gelen Tink nesnelerini en kısa sürede oluşturun.

Türlü kullanım:

KeysetHandle readEcdsaKeyFromFile(Path fileWithEcdsaKey) throws Exception {
    byte[] content = Files.readAllBytes(fileWithEcdsaKey);
    BigInteger x = new BigInteger(1, Arrays.copyOfRange(content, 0, 32));
    BigInteger y = new BigInteger(1, Arrays.copyOfRange(content, 32, 64));
    ECPoint point = new ECPoint(x, y);
    EcdsaParameters parameters =
        EcdsaParameters.builder()
            .setSignatureEncoding(EcdsaParameters.SignatureEncoding.IEEE_P1363)
            .setCurveType(EcdsaParameters.CurveType.NIST_P256)
            .setHashType(EcdsaParameters.HashType.SHA256)
            .setVariant(EcdsaParameters.Variant.NO_PREFIX)
            .build();
    EcdsaPublicKey key =
        EcdsaPublicKey.builder()
            .setParameters(parameters)
            .setPublicPoint(ecPoint)
            .build();
    return KeysetHandle.newBuilder()
       .addEntry(KeysetHandle.importKey(key).withFixedId(1).makePrimary())
       .build();
}

Bu tür bir kod kullanarak bayt dizisini, okuma sırasında hemen bir Tink nesnesine dönüştürür ve hangi algoritmanın kullanılması gerektiğini tam olarak belirtiriz. Bu yaklaşım, önemli kafa karışıklığı saldırıları olasılığını en aza indirir.

En İyi Uygulama: Anahtar dışa aktarma işleminde tüm parametreleri doğrulama

Örneğin, HPKE ortak anahtarını dışa aktaran bir işlev yazarsanız:

Ortak anahtarı dışa aktarmanın kötü yolu:

/** Provide the key to our users which do not have Tink. */
byte[] exportTinkHpkeKey(HpkePublicKey key) {
    return key.getPublicKeyBytes().toByteArray();
}

Bu sorunlu bir durumdur. Anahtarı aldıktan sonra, bunu kullanan üçüncü taraf, anahtarın parametreleri hakkında bazı varsayımlarda bulunur: Örneğin, bu anahtar 256 bit için kullanılan HPKE AEAD algoritmasının AES-GCM olduğunu varsayar ve bu şekilde devam eder.

Öneri: Parametrelerin, anahtar dışa aktarma işleminde beklediğiniz şekilde olduğunu doğrulayın.

Ortak anahtarı dışa aktarmanın daha iyi yolu:

/** Provide the key to our users which do not have Tink. */
byte[] exportTinkHpkeKeyForOurUsers(HpkePublicKey key) {
    // Our users assume we use KEM_P256_HKDF_SHA256 for the KEM.
    if (!key.getParameters().getKemId().equals(HpkeParameters.KemId.KEM_P256_HKDF_SHA256)) {
        throw new IllegalArgumentException("Bad parameters");
    }
    // Our users assume we use HKDF SHA256 to create the key material.
    if (!key.getParameters().getKdfId().equals(HpkeParameters.KdfId.HKDF_SHA256)) {
        throw new IllegalArgumentException("Bad parameters");
    }
    // Our users assume that we use AES GCM with 256 bit keys.
    if (!key.getParameters().getAeadId().equals(HpkeParameters.AeadId.AES_256_GCM)) {
        throw new IllegalArgumentException("Bad parameters");
    }
    // Our users assume we follow the standard and do not add a Tink style prefix
    if (!key.getParameters().getVariant().equals(HpkeParameters.Variant.NO_PREFIX)) {
        throw new IllegalArgumentException("Bad parameters");
    }
    return key.getPublicKeyBytes().toByteArray();
}