Merge "Add more examples of generating/importing keys to Javadocs." into mnc-dev
This commit is contained in:
@ -38,27 +38,35 @@ import javax.security.auth.x500.X500Principal;
|
||||
/**
|
||||
* {@link AlgorithmParameterSpec} for initializing a {@link KeyPairGenerator} or a
|
||||
* {@link KeyGenerator} of the <a href="{@docRoot}training/articles/keystore.html">Android Keystore
|
||||
* system</a>. The spec determines whether user authentication is required for using the key, what
|
||||
* uses the key is authorized for (e.g., only for signing -- decryption not permitted), the key's
|
||||
* validity start and end dates.
|
||||
* system</a>. The spec determines authorized uses of the key, such as whether user authentication
|
||||
* is required for using the key, what operations are authorized (e.g., signing, but not
|
||||
* decryption) and with what parameters (e.g., only with a particular padding scheme or digest), the
|
||||
* key's validity start and end dates. Key use authorizations expressed in the spec apply only to
|
||||
* secret keys and private keys -- public keys can be used for any supported operations.
|
||||
*
|
||||
* <p>To generate an asymmetric key pair or a symmetric key, create an instance of this class using
|
||||
* the {@link Builder}, initialize a {@code KeyPairGenerator} or a {@code KeyGenerator} of the
|
||||
* desired key type (e.g., {@code EC} or {@code AES} -- see
|
||||
* {@link KeyProperties}.{@code KEY_ALGORITHM} constants) from the {@code AndroidKeyStore} provider
|
||||
* with the {@code KeyPairGeneratorSpec} instance, and then generate a key or key pair using
|
||||
* {@link KeyPairGenerator#generateKeyPair()}.
|
||||
* with the {@code KeyGenParameterSpec} instance, and then generate a key or key pair using
|
||||
* {@link KeyGenerator#generateKey()} or {@link KeyPairGenerator#generateKeyPair()}.
|
||||
*
|
||||
* <p>The generated key pair or key will be returned by the generator and also stored in the Android
|
||||
* Keystore system under the alias specified in this spec. To obtain the secret or private key from
|
||||
* the Android KeyStore use {@link java.security.KeyStore#getKey(String, char[]) KeyStore.getKey(String, null)}
|
||||
* Keystore under the alias specified in this spec. To obtain the secret or private key from the
|
||||
* Android Keystore use {@link java.security.KeyStore#getKey(String, char[]) KeyStore.getKey(String, null)}
|
||||
* or {@link java.security.KeyStore#getEntry(String, java.security.KeyStore.ProtectionParameter) KeyStore.getEntry(String, null)}.
|
||||
* To obtain the public key from the Android Keystore system use
|
||||
* To obtain the public key from the Android Keystore use
|
||||
* {@link java.security.KeyStore#getCertificate(String)} and then
|
||||
* {@link Certificate#getPublicKey()}.
|
||||
*
|
||||
* <p>To help obtain algorithm-specific public parameters of key pairs stored in the Android
|
||||
* Keystore, generated private keys implement {@link java.security.interfaces.ECKey} or
|
||||
* {@link java.security.interfaces.RSAKey} interfaces whereas public keys implement
|
||||
* {@link java.security.interfaces.ECPublicKey} or {@link java.security.interfaces.RSAPublicKey}
|
||||
* interfaces.
|
||||
*
|
||||
* <p>For asymmetric key pairs, a self-signed X.509 certificate will be also generated and stored in
|
||||
* the Android KeyStore. This is because the {@link java.security.KeyStore} abstraction does not
|
||||
* the Android Keystore. This is because the {@link java.security.KeyStore} abstraction does not
|
||||
* support storing key pairs without a certificate. The subject, serial number, and validity dates
|
||||
* of the certificate can be customized in this spec. The self-signed certificate may be replaced at
|
||||
* a later time by a certificate signed by a Certificate Authority (CA).
|
||||
@ -82,27 +90,33 @@ import javax.security.auth.x500.X500Principal;
|
||||
*
|
||||
* <p>Instances of this class are immutable.
|
||||
*
|
||||
* <p><h3>Example: Asymmetric key pair</h3>
|
||||
* The following example illustrates how to generate an EC key pair in the Android KeyStore system
|
||||
* under alias {@code key1} authorized to be used only for signing using SHA-256, SHA-384,
|
||||
* or SHA-512 digest and only if the user has been authenticated within the last five minutes.
|
||||
* <p><h3>Example: NIST P-256 EC key pair for signing/verification using ECDSA</h3>
|
||||
* This example illustrates how to generate a NIST P-256 (aka secp256r1 aka prime256v1) EC key pair
|
||||
* in the Android KeyStore system under alias {@code key1} where the private key is authorized to be
|
||||
* used only for signing using SHA-256, SHA-384, or SHA-512 digest and only if the user has been
|
||||
* authenticated within the last five minutes. The use of public key is unrestricted, thus
|
||||
* permitting signature verification using any padding schemes and digests, and without user
|
||||
* authentication.
|
||||
* <pre> {@code
|
||||
* KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(
|
||||
* KeyProperties.KEY_ALGORITHM_EC,
|
||||
* "AndroidKeyStore");
|
||||
* KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
|
||||
* keyPairGenerator.initialize(
|
||||
* new KeyGenParameterSpec.Builder(
|
||||
* "key1",
|
||||
* KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
|
||||
* KeyProperties.PURPOSE_SIGN)
|
||||
* .setAlgorithmParameterSpec(new ECGenParameterSpec("secp256r1"))
|
||||
* .setDigests(KeyProperties.DIGEST_SHA256,
|
||||
* KeyProperties.DIGEST_SHA384,
|
||||
* KeyProperties.DIGEST_SHA512)
|
||||
* // Only permit this key to be used if the user authenticated
|
||||
* // Only permit the private key to be used if the user authenticated
|
||||
* // within the last five minutes.
|
||||
* .setUserAuthenticationRequired(true)
|
||||
* .setUserAuthenticationValidityDurationSeconds(5 * 60)
|
||||
* .build());
|
||||
* KeyPair keyPair = keyPairGenerator.generateKeyPair();
|
||||
* Signature signature = Signature.getInstance("SHA256withECDSA");
|
||||
* signature.initSign(keyPair.getPrivate());
|
||||
* ...
|
||||
*
|
||||
* // The key pair can also be obtained from the Android Keystore any time as follows:
|
||||
* KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
|
||||
@ -111,14 +125,67 @@ import javax.security.auth.x500.X500Principal;
|
||||
* PublicKey publicKey = keyStore.getCertificate("key1").getPublicKey();
|
||||
* }</pre>
|
||||
*
|
||||
* <p><h3>Example: Symmetric key</h3>
|
||||
* <p><h3>Example: RSA key pair for signing/verification using RSA-PSS</h3>
|
||||
* This example illustrates how to generate an RSA key pair in the Android KeyStore system under
|
||||
* alias {@code key1} authorized to be used only for signing using the RSA-PSS signature padding
|
||||
* scheme with SHA-256 or SHA-512 digests. The use of public key is unrestricted, thus permitting
|
||||
* signature verification using any padding schemes and digests.
|
||||
* <pre> {@code
|
||||
* KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(
|
||||
* KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
|
||||
* keyPairGenerator.initialize(
|
||||
* new KeyGenParameterSpec.Builder(
|
||||
* "key1",
|
||||
* KeyProperties.PURPOSE_SIGN)
|
||||
* .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
|
||||
* .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PSS)
|
||||
* .build());
|
||||
* KeyPair keyPair = keyPairGenerator.generateKeyPair();
|
||||
* Signature signature = Signature.getInstance("SHA256withRSA/PSS");
|
||||
* signature.initSign(keyPair.getPrivate());
|
||||
* ...
|
||||
*
|
||||
* // The key pair can also be obtained from the Android Keystore any time as follows:
|
||||
* KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
|
||||
* keyStore.load(null);
|
||||
* PrivateKey privateKey = (PrivateKey) keyStore.getKey("key1", null);
|
||||
* PublicKey publicKey = keyStore.getCertificate("key1").getPublicKey();
|
||||
* }</pre>
|
||||
*
|
||||
* <p><h3>Example: RSA key pair for encryption/decryption using RSA OAEP</h3>
|
||||
* This example illustrates how to generate an RSA key pair in the Android KeyStore system under
|
||||
* alias {@code key1} where the private key is authorized to be used only for decryption using RSA
|
||||
* OAEP encryption padding scheme with SHA-256 or SHA-512 digests. The use of public key is
|
||||
* unrestricted, thus permitting encryption using any padding schemes and digests.
|
||||
* <pre> {@code
|
||||
* KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(
|
||||
* KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
|
||||
* keyPairGenerator.initialize(
|
||||
* new KeyGenParameterSpec.Builder(
|
||||
* "key1",
|
||||
* KeyProperties.PURPOSE_DECRYPT)
|
||||
* .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
|
||||
* .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
|
||||
* .build());
|
||||
* KeyPair keyPair = keyPairGenerator.generateKeyPair();
|
||||
* Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
|
||||
* cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
|
||||
* ...
|
||||
*
|
||||
* // The key pair can also be obtained from the Android Keystore any time as follows:
|
||||
* KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
|
||||
* keyStore.load(null);
|
||||
* PrivateKey privateKey = (PrivateKey) keyStore.getKey("key1", null);
|
||||
* PublicKey publicKey = keyStore.getCertificate("key1").getPublicKey();
|
||||
* }</pre>
|
||||
*
|
||||
* <p><h3>Example: AES key for encryption/decryption in GCM mode</h3>
|
||||
* The following example illustrates how to generate an AES key in the Android KeyStore system under
|
||||
* alias {@code key2} authorized to be used only for encryption/decryption in GCM mode with no
|
||||
* padding.
|
||||
* <pre> {@code
|
||||
* KeyGenerator keyGenerator = KeyGenerator.getInstance(
|
||||
* KeyProperties.KEY_ALGORITHM_AES,
|
||||
* "AndroidKeyStore");
|
||||
* KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
|
||||
* keyGenerator.initialize(
|
||||
* new KeyGenParameterSpec.Builder("key2",
|
||||
* KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
|
||||
@ -127,6 +194,29 @@ import javax.security.auth.x500.X500Principal;
|
||||
* .build());
|
||||
* SecretKey key = keyGenerator.generateKey();
|
||||
*
|
||||
* Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
* cipher.init(Cipher.ENCRYPT_MODE, key);
|
||||
* ...
|
||||
*
|
||||
* // The key can also be obtained from the Android Keystore any time as follows:
|
||||
* KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
|
||||
* keyStore.load(null);
|
||||
* key = (SecretKey) keyStore.getKey("key2", null);
|
||||
* }</pre>
|
||||
*
|
||||
* <p><h3>Example: HMAC key for generating a MAC using SHA-256</h3>
|
||||
* This example illustrates how to generate an HMAC key in the Android KeyStore system under alias
|
||||
* {@code key2} authorized to be used only for generating an HMAC using SHA-256.
|
||||
* <pre> {@code
|
||||
* KeyGenerator keyGenerator = KeyGenerator.getInstance(
|
||||
* KeyProperties.KEY_ALGORITHM_HMAC_SHA256, "AndroidKeyStore");
|
||||
* keyGenerator.initialize(
|
||||
* new KeyGenParameterSpec.Builder("key2", KeyProperties.PURPOSE_SIGN).build());
|
||||
* SecretKey key = keyGenerator.generateKey();
|
||||
* Mac mac = Mac.getInstance("HmacSHA256");
|
||||
* mac.init(key);
|
||||
* ...
|
||||
*
|
||||
* // The key can also be obtained from the Android Keystore any time as follows:
|
||||
* KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
|
||||
* keyStore.load(null);
|
||||
|
@ -33,28 +33,36 @@ import javax.crypto.Mac;
|
||||
|
||||
/**
|
||||
* Specification of how a key or key pair is secured when imported into the
|
||||
* <a href="{@docRoot}training/articles/keystore.html">Android KeyStore facility</a>. This class
|
||||
* specifies parameters such as whether user authentication is required for using the key, what uses
|
||||
* the key is authorized for (e.g., only in {@code GCM} mode, or only for signing -- decryption not
|
||||
* permitted), the key's and validity start and end dates.
|
||||
* <a href="{@docRoot}training/articles/keystore.html">Android Keystore system</a>. This class
|
||||
* specifies authorized uses of the imported key, such as whether user authentication is required
|
||||
* for using the key, what operations the key is authorized for (e.g., decryption, but not signing)
|
||||
* and with what parameters (e.g., only with a particular padding scheme or digest), the key's and
|
||||
* validity start and end dates. Key use authorizations expressed in this class apply only to secret
|
||||
* keys and private keys -- public keys can be used for any supported operations.
|
||||
*
|
||||
* <p>To import a key or key pair into the Android KeyStore, create an instance of this class using
|
||||
* <p>To import a key or key pair into the Android Keystore, create an instance of this class using
|
||||
* the {@link Builder} and pass the instance into {@link java.security.KeyStore#setEntry(String, java.security.KeyStore.Entry, ProtectionParameter) KeyStore.setEntry}
|
||||
* with the key or key pair being imported.
|
||||
*
|
||||
* <p>To obtain the secret/symmetric or private key from the Android KeyStore use
|
||||
* <p>To obtain the secret/symmetric or private key from the Android Keystore use
|
||||
* {@link java.security.KeyStore#getKey(String, char[]) KeyStore.getKey(String, null)} or
|
||||
* {@link java.security.KeyStore#getEntry(String, java.security.KeyStore.ProtectionParameter) KeyStore.getEntry(String, null)}.
|
||||
* To obtain the public key from the Android KeyStore use
|
||||
* To obtain the public key from the Android Keystore use
|
||||
* {@link java.security.KeyStore#getCertificate(String)} and then
|
||||
* {@link Certificate#getPublicKey()}.
|
||||
*
|
||||
* <p>NOTE: The key material of keys stored in the Android KeyStore is not accessible.
|
||||
* <p>To help obtain algorithm-specific public parameters of key pairs stored in the Android
|
||||
* Keystore, its private keys implement {@link java.security.interfaces.ECKey} or
|
||||
* {@link java.security.interfaces.RSAKey} interfaces whereas its public keys implement
|
||||
* {@link java.security.interfaces.ECPublicKey} or {@link java.security.interfaces.RSAPublicKey}
|
||||
* interfaces.
|
||||
*
|
||||
* <p>NOTE: The key material of keys stored in the Android Keystore is not accessible.
|
||||
*
|
||||
* <p>Instances of this class are immutable.
|
||||
*
|
||||
* <p><h3>Example: Symmetric Key</h3>
|
||||
* The following example illustrates how to import an AES key into the Android KeyStore under alias
|
||||
* <p><h3>Example: AES key for encryption/decryption in GCM mode</h3>
|
||||
* This example illustrates how to import an AES key into the Android KeyStore under alias
|
||||
* {@code key1} authorized to be used only for encryption/decryption in GCM mode with no padding.
|
||||
* The key must export its key material via {@link Key#getEncoded()} in {@code RAW} format.
|
||||
* <pre> {@code
|
||||
@ -71,15 +79,41 @@ import javax.crypto.Mac;
|
||||
* .build());
|
||||
* // Key imported, obtain a reference to it.
|
||||
* SecretKey keyStoreKey = (SecretKey) keyStore.getKey("key1", null);
|
||||
* // The original key can now be thrown away.
|
||||
* // The original key can now be discarded.
|
||||
*
|
||||
* Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
* cipher.init(Cipher.ENCRYPT_MODE, keyStoreKey);
|
||||
* ...
|
||||
* }</pre>
|
||||
*
|
||||
* <p><h3>Example: Asymmetric Key Pair</h3>
|
||||
* The following example illustrates how to import an EC key pair into the Android KeyStore under
|
||||
* alias {@code key2} authorized to be used only for signing with SHA-256 digest and only if
|
||||
* the user has been authenticated within the last ten minutes. Both the private and the public key
|
||||
* must export their key material via {@link Key#getEncoded()} in {@code PKCS#8} and {@code X.509}
|
||||
* format respectively.
|
||||
* <p><h3>Example: HMAC key for generating MACs using SHA-512</h3>
|
||||
* This example illustrates how to import an HMAC key into the Android KeyStore under alias
|
||||
* {@code key1} authorized to be used only for generating MACs using SHA-512 digest. The key must
|
||||
* export its key material via {@link Key#getEncoded()} in {@code RAW} format.
|
||||
* <pre> {@code
|
||||
* SecretKey key = ...; // HMAC key of algorithm "HmacSHA512".
|
||||
*
|
||||
* KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
|
||||
* keyStore.load(null);
|
||||
* keyStore.setEntry(
|
||||
* "key1",
|
||||
* new KeyStore.SecretKeyEntry(key),
|
||||
* new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN).build());
|
||||
* // Key imported, obtain a reference to it.
|
||||
* SecretKey keyStoreKey = (SecretKey) keyStore.getKey("key1", null);
|
||||
* // The original key can now be discarded.
|
||||
*
|
||||
* Mac mac = Mac.getInstance("HmacSHA512");
|
||||
* mac.init(keyStoreKey);
|
||||
* ...
|
||||
* }</pre>
|
||||
*
|
||||
* <p><h3>Example: EC key pair for signing/verification using ECDSA</h3>
|
||||
* This example illustrates how to import an EC key pair into the Android KeyStore under alias
|
||||
* {@code key2} with the private key authorized to be used only for signing with SHA-256 or SHA-512
|
||||
* digests. The use of public key is unrestricted, thus permitting signature verification using any
|
||||
* digests. Both the private and the public key must export their key material via
|
||||
* {@link Key#getEncoded()} in {@code PKCS#8} and {@code X.509} format respectively.
|
||||
* <pre> {@code
|
||||
* PrivateKey privateKey = ...; // EC private key
|
||||
* Certificate[] certChain = ...; // Certificate chain with the first certificate
|
||||
@ -91,7 +125,39 @@ import javax.crypto.Mac;
|
||||
* "key2",
|
||||
* new KeyStore.PrivateKeyEntry(privateKey, certChain),
|
||||
* new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN)
|
||||
* .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
|
||||
* .build());
|
||||
* // Key pair imported, obtain a reference to it.
|
||||
* PrivateKey keyStorePrivateKey = (PrivateKey) keyStore.getKey("key2", null);
|
||||
* PublicKey publicKey = keyStore.getCertificate("key2").getPublicKey();
|
||||
* // The original private key can now be discarded.
|
||||
*
|
||||
* Signature signature = Signature.getInstance("SHA256withECDSA");
|
||||
* signature.initSign(keyStorePrivateKey);
|
||||
* ...
|
||||
* }</pre>
|
||||
*
|
||||
* <p><h3>Example: RSA key pair for signing/verification using PKCS#1 padding</h3>
|
||||
* This example illustrates how to import an RSA key pair into the Android KeyStore under alias
|
||||
* {@code key2} with the private key authorized to be used only for signing using the PKCS#1
|
||||
* signature padding scheme with SHA-256 digest and only if the user has been authenticated within
|
||||
* the last ten minutes. The use of public key is unrestricted, thus permitting signature
|
||||
* verification using any padding schemes and digests, and without user authentication. Both the
|
||||
* private and the public key must export their key material via {@link Key#getEncoded()} in
|
||||
* {@code PKCS#8} and {@code X.509} format respectively.
|
||||
* <pre> {@code
|
||||
* PrivateKey privateKey = ...; // RSA private key
|
||||
* Certificate[] certChain = ...; // Certificate chain with the first certificate
|
||||
* // containing the corresponding RSA public key.
|
||||
*
|
||||
* KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
|
||||
* keyStore.load(null);
|
||||
* keyStore.setEntry(
|
||||
* "key2",
|
||||
* new KeyStore.PrivateKeyEntry(privateKey, certChain),
|
||||
* new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN)
|
||||
* .setDigests(KeyProperties.DIGEST_SHA256)
|
||||
* .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
|
||||
* // Only permit this key to be used if the user
|
||||
* // authenticated within the last ten minutes.
|
||||
* .setUserAuthenticationRequired(true)
|
||||
@ -100,7 +166,40 @@ import javax.crypto.Mac;
|
||||
* // Key pair imported, obtain a reference to it.
|
||||
* PrivateKey keyStorePrivateKey = (PrivateKey) keyStore.getKey("key2", null);
|
||||
* PublicKey publicKey = keyStore.getCertificate("key2").getPublicKey();
|
||||
* // The original private key can now be thrown away.
|
||||
* // The original private key can now be discarded.
|
||||
*
|
||||
* Signature signature = Signature.getInstance("SHA256withRSA");
|
||||
* signature.initSign(keyStorePrivateKey);
|
||||
* ...
|
||||
* }</pre>
|
||||
*
|
||||
* <p><h3>Example: RSA key pair for encryption/decryption using PKCS#1 padding</h3>
|
||||
* This example illustrates how to import an RSA key pair into the Android KeyStore under alias
|
||||
* {@code key2} with the private key authorized to be used only for decryption using the PKCS#1
|
||||
* encryption padding scheme. The use of public key is unrestricted, thus permitting encryption
|
||||
* using any padding schemes and digests. Both the private and the public key must export their key
|
||||
* material via {@link Key#getEncoded()} in {@code PKCS#8} and {@code X.509} format respectively.
|
||||
* <pre> {@code
|
||||
* PrivateKey privateKey = ...; // RSA private key
|
||||
* Certificate[] certChain = ...; // Certificate chain with the first certificate
|
||||
* // containing the corresponding RSA public key.
|
||||
*
|
||||
* KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
|
||||
* keyStore.load(null);
|
||||
* keyStore.setEntry(
|
||||
* "key2",
|
||||
* new KeyStore.PrivateKeyEntry(privateKey, certChain),
|
||||
* new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
|
||||
* .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
|
||||
* .build());
|
||||
* // Key pair imported, obtain a reference to it.
|
||||
* PrivateKey keyStorePrivateKey = (PrivateKey) keyStore.getKey("key2", null);
|
||||
* PublicKey publicKey = keyStore.getCertificate("key2").getPublicKey();
|
||||
* // The original private key can now be discarded.
|
||||
*
|
||||
* Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
|
||||
* cipher.init(Cipher.DECRYPT_MODE, keyStorePrivateKey);
|
||||
* ...
|
||||
* }</pre>
|
||||
*/
|
||||
public final class KeyProtection implements ProtectionParameter {
|
||||
|
Reference in New Issue
Block a user