Some more ECDSA documentation.
This commit is contained in:
@@ -1,30 +1,54 @@
|
|||||||
use cryptonum::signed::{I192,I256,I384,I576};
|
use cryptonum::signed::{I192,I256,I384,I576};
|
||||||
use cryptonum::unsigned::{Decoder};
|
use cryptonum::unsigned::{Decoder};
|
||||||
use cryptonum::unsigned::{U192,U256,U384,U576};
|
use cryptonum::unsigned::{U192,U256,U384,U576};
|
||||||
|
use ecdsa::point::Point;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
/// Elliptic curves must implement this trait in order to work with the rest
|
||||||
|
/// of the ECDSA system. I've included instances for the core NIST curves
|
||||||
|
/// used in most systems, but this could be extended without issues.
|
||||||
|
/// (Eventually the curves defined here should actually be extended in
|
||||||
|
/// interesting ways to make the math faster, but we haven't gotten there
|
||||||
|
/// yet.)
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub trait EllipticCurve {
|
pub trait EllipticCurve {
|
||||||
|
/// The unsigned numeric type that fits constants for this curve.
|
||||||
type Unsigned : Clone;
|
type Unsigned : Clone;
|
||||||
|
/// The signed numeric type that fits constants for this curve.
|
||||||
type Signed : Clone + Debug + PartialEq;
|
type Signed : Clone + Debug + PartialEq;
|
||||||
|
/// The type of a point on the curve
|
||||||
|
type Point;
|
||||||
|
|
||||||
|
/// The size of the curve in bits.
|
||||||
fn size() -> usize;
|
fn size() -> usize;
|
||||||
|
/// The `p` value for the curve.
|
||||||
fn p() -> Self::Unsigned;
|
fn p() -> Self::Unsigned;
|
||||||
|
/// The `p` value for the curve.
|
||||||
fn n() -> Self::Unsigned;
|
fn n() -> Self::Unsigned;
|
||||||
|
/// The seed value for the curve.
|
||||||
fn SEED() -> Self::Unsigned;
|
fn SEED() -> Self::Unsigned;
|
||||||
|
/// The `c` value for the curve.
|
||||||
fn c() -> Self::Unsigned;
|
fn c() -> Self::Unsigned;
|
||||||
|
/// The `a` value for the curve.
|
||||||
fn a() -> Self::Unsigned;
|
fn a() -> Self::Unsigned;
|
||||||
|
/// The `b` value for the curve.
|
||||||
fn b() -> Self::Unsigned;
|
fn b() -> Self::Unsigned;
|
||||||
|
/// The `x` coordinate of the base point for the curve.
|
||||||
fn Gx() -> Self::Signed;
|
fn Gx() -> Self::Signed;
|
||||||
|
/// The `y` coordinate of the base point for the curve.
|
||||||
fn Gy() -> Self::Signed;
|
fn Gy() -> Self::Signed;
|
||||||
|
/// Generate a point for the curve given the provided values.
|
||||||
|
fn new_point(x: Self::Unsigned, y: Self::Unsigned) -> Self::Point;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// NIST curve P-192 (FIPS 186-4, page 101-102), a.k.a. secp192r1 from RFC5480
|
||||||
#[derive(Debug,PartialEq)]
|
#[derive(Debug,PartialEq)]
|
||||||
pub enum P192 {}
|
pub struct P192 {}
|
||||||
|
|
||||||
impl EllipticCurve for P192 {
|
impl EllipticCurve for P192 {
|
||||||
type Unsigned = U192;
|
type Unsigned = U192;
|
||||||
type Signed = I192;
|
type Signed = I192;
|
||||||
|
type Point = Point<P192>;
|
||||||
|
|
||||||
fn size() -> usize {
|
fn size() -> usize {
|
||||||
192
|
192
|
||||||
@@ -61,14 +85,20 @@ impl EllipticCurve for P192 {
|
|||||||
fn Gy() -> I192 {
|
fn Gy() -> I192 {
|
||||||
I192::from(U192::from([0x73f977a11e794811, 0x631011ed6b24cdd5, 0x07192b95ffc8da78]))
|
I192::from(U192::from([0x73f977a11e794811, 0x631011ed6b24cdd5, 0x07192b95ffc8da78]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn new_point(x: Self::Unsigned, y: Self::Unsigned) -> Self::Point {
|
||||||
|
Point::<P192>{ x: I192::from(x), y: I192::from(y) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// NIST curve P-224 (FIPS 186-4, page 102), a.k.a. secp224r1 from RFC5480
|
||||||
#[derive(Debug,PartialEq)]
|
#[derive(Debug,PartialEq)]
|
||||||
pub enum P224 {}
|
pub struct P224 {}
|
||||||
|
|
||||||
impl EllipticCurve for P224 {
|
impl EllipticCurve for P224 {
|
||||||
type Unsigned = U256;
|
type Unsigned = U256;
|
||||||
type Signed = I256;
|
type Signed = I256;
|
||||||
|
type Point = Point<P224>;
|
||||||
|
|
||||||
fn size() -> usize {
|
fn size() -> usize {
|
||||||
224
|
224
|
||||||
@@ -144,14 +174,20 @@ impl EllipticCurve for P224 {
|
|||||||
0x85, 0x00, 0x7e, 0x34
|
0x85, 0x00, 0x7e, 0x34
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn new_point(x: Self::Unsigned, y: Self::Unsigned) -> Self::Point {
|
||||||
|
Point::<P224>{ x: I256::from(x), y: I256::from(y) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// NIST curve P-256 (FIPS 186-4, page 102-103), a.k.a. secp256r1 from RFC5480
|
||||||
#[derive(Debug,PartialEq)]
|
#[derive(Debug,PartialEq)]
|
||||||
pub enum P256 {}
|
pub struct P256 {}
|
||||||
|
|
||||||
impl EllipticCurve for P256 {
|
impl EllipticCurve for P256 {
|
||||||
type Signed = I256;
|
type Signed = I256;
|
||||||
type Unsigned = U256;
|
type Unsigned = U256;
|
||||||
|
type Point = Point<P256>;
|
||||||
|
|
||||||
fn size() -> usize {
|
fn size() -> usize {
|
||||||
256
|
256
|
||||||
@@ -228,14 +264,20 @@ impl EllipticCurve for P256 {
|
|||||||
0xcb, 0xb6, 0x40, 0x68, 0x37, 0xbf, 0x51, 0xf5
|
0xcb, 0xb6, 0x40, 0x68, 0x37, 0xbf, 0x51, 0xf5
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn new_point(x: Self::Unsigned, y: Self::Unsigned) -> Self::Point {
|
||||||
|
Point::<P256>{ x: I256::from(x), y: I256::from(y) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// NIST curve P-384 (FIPS 186-4, page 103-104), a.k.a. secp384r1 from RFC5480
|
||||||
#[derive(Debug,PartialEq)]
|
#[derive(Debug,PartialEq)]
|
||||||
pub enum P384 {}
|
pub struct P384 {}
|
||||||
|
|
||||||
impl EllipticCurve for P384 {
|
impl EllipticCurve for P384 {
|
||||||
type Signed = I384;
|
type Signed = I384;
|
||||||
type Unsigned = U384;
|
type Unsigned = U384;
|
||||||
|
type Point = Point<P384>;
|
||||||
|
|
||||||
fn size() -> usize {
|
fn size() -> usize {
|
||||||
384
|
384
|
||||||
@@ -325,14 +367,20 @@ impl EllipticCurve for P384 {
|
|||||||
0x7a, 0x43, 0x1d, 0x7c, 0x90, 0xea, 0x0e, 0x5f
|
0x7a, 0x43, 0x1d, 0x7c, 0x90, 0xea, 0x0e, 0x5f
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn new_point(x: Self::Unsigned, y: Self::Unsigned) -> Self::Point {
|
||||||
|
Point::<P384>{ x: I384::from(x), y: I384::from(y) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// NIST curve P-521 (FIPS 186-4, page 104), a.k.a. secp521r1 from RFC5480
|
||||||
#[derive(Debug,PartialEq)]
|
#[derive(Debug,PartialEq)]
|
||||||
pub enum P521 {}
|
pub struct P521 {}
|
||||||
|
|
||||||
impl EllipticCurve for P521 {
|
impl EllipticCurve for P521 {
|
||||||
type Signed = I576;
|
type Signed = I576;
|
||||||
type Unsigned = U576;
|
type Unsigned = U576;
|
||||||
|
type Point = Point<P521>;
|
||||||
|
|
||||||
fn size() -> usize {
|
fn size() -> usize {
|
||||||
521
|
521
|
||||||
@@ -443,4 +491,8 @@ impl EllipticCurve for P521 {
|
|||||||
0x66, 0x50
|
0x66, 0x50
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn new_point(x: Self::Unsigned, y: Self::Unsigned) -> Self::Point {
|
||||||
|
Point::<P521>{ x: I576::from(x), y: I576::from(y) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,28 @@
|
|||||||
pub mod curve;
|
mod curve;
|
||||||
pub mod point;
|
pub(crate) mod point;
|
||||||
pub mod private;
|
mod private;
|
||||||
pub mod public;
|
mod public;
|
||||||
|
|
||||||
use cryptonum::signed::{I192,I256,I384,I576};
|
use cryptonum::signed::{I192,I256,I384,I576};
|
||||||
use cryptonum::unsigned::{CryptoNum,Decoder};
|
use cryptonum::unsigned::{CryptoNum,Decoder};
|
||||||
use cryptonum::unsigned::{U192,U256,U384,U576};
|
use cryptonum::unsigned::{U192,U256,U384,U576};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use rand::distributions::Standard;
|
use rand::distributions::Standard;
|
||||||
use self::curve::{EllipticCurve,P192,P224,P256,P384,P521};
|
pub use self::curve::{EllipticCurve,P192,P224,P256,P384,P521};
|
||||||
use self::point::{ECCPoint,Point};
|
use self::point::{ECCPoint,Point};
|
||||||
pub use self::private::{ECDSAPrivate,ECCPrivateKey};
|
pub use self::private::{ECDSAPrivate,ECCPrivateKey};
|
||||||
pub use self::public::{ECDSAPublic,ECCPublicKey};
|
pub use self::public::{ECDSAPublic,ECCPublicKey};
|
||||||
pub use self::public::{ECDSADecodeErr,ECDSAEncodeErr};
|
pub use self::public::{ECDSADecodeErr,ECDSAEncodeErr};
|
||||||
use super::KeyPair;
|
use super::KeyPair;
|
||||||
|
|
||||||
|
/// An ECDSA key pair for the given curve.
|
||||||
pub struct ECDSAKeyPair<Curve: EllipticCurve> {
|
pub struct ECDSAKeyPair<Curve: EllipticCurve> {
|
||||||
pub public: ECCPublicKey<Curve>,
|
pub public: ECCPublicKey<Curve>,
|
||||||
pub private: ECCPrivateKey<Curve>
|
pub private: ECCPrivateKey<Curve>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A generic ECDSA key pair that implements one of our known curves, for cases
|
||||||
|
/// when you're not sure which one you're going to have.
|
||||||
pub enum ECDSAPair {
|
pub enum ECDSAPair {
|
||||||
P192(ECCPublicKey<P192>,ECCPrivateKey<P192>),
|
P192(ECCPublicKey<P192>,ECCPrivateKey<P192>),
|
||||||
P224(ECCPublicKey<P224>,ECCPrivateKey<P224>),
|
P224(ECCPublicKey<P224>,ECCPrivateKey<P224>),
|
||||||
@@ -57,7 +60,12 @@ macro_rules! generate_impl {
|
|||||||
ECDSAKeyPair{ public, private }
|
ECDSAKeyPair{ public, private }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ECDSAKeyPair<$curve> {
|
impl ECDSAKeyPair<$curve> {
|
||||||
|
/// Generate a fresh ECDSA key pair for this curve, given the
|
||||||
|
/// provided random number generator. THIS MUST BE A CRYPTO
|
||||||
|
/// STRONG RNG. If it's not, then you're going to generate weak
|
||||||
|
/// keys and the crypto gremlins will get you.
|
||||||
pub fn generate<G: Rng>(rng: &mut G) -> ECDSAKeyPair<$curve>
|
pub fn generate<G: Rng>(rng: &mut G) -> ECDSAKeyPair<$curve>
|
||||||
{
|
{
|
||||||
loop {
|
loop {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use ecdsa::point::{ECCPoint,Point};
|
|||||||
use hmac::{Hmac,Mac};
|
use hmac::{Hmac,Mac};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
/// A private key for the given curve.
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq)]
|
||||||
pub struct ECCPrivateKey<Curve: EllipticCurve> {
|
pub struct ECCPrivateKey<Curve: EllipticCurve> {
|
||||||
pub(crate) d: Curve::Unsigned
|
pub(crate) d: Curve::Unsigned
|
||||||
@@ -19,6 +20,7 @@ impl<Curve: EllipticCurve> fmt::Debug for ECCPrivateKey<Curve> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A generic private key.
|
||||||
pub enum ECDSAPrivate {
|
pub enum ECDSAPrivate {
|
||||||
P192(ECCPrivateKey<P192>),
|
P192(ECCPrivateKey<P192>),
|
||||||
P224(ECCPrivateKey<P224>),
|
P224(ECCPrivateKey<P224>),
|
||||||
@@ -32,11 +34,14 @@ macro_rules! generate_privates
|
|||||||
($curve: ident, $base: ident, $sig: ident, $dbl: ident, $quad: ident) => {
|
($curve: ident, $base: ident, $sig: ident, $dbl: ident, $quad: ident) => {
|
||||||
impl ECCPrivateKey<$curve>
|
impl ECCPrivateKey<$curve>
|
||||||
{
|
{
|
||||||
|
/// Generate a new private key using the given private scalar.
|
||||||
pub fn new(d: $base) -> ECCPrivateKey<$curve>
|
pub fn new(d: $base) -> ECCPrivateKey<$curve>
|
||||||
{
|
{
|
||||||
ECCPrivateKey{ d }
|
ECCPrivateKey{ d }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sign the given message with the current key, using the hash provided
|
||||||
|
/// in the type.
|
||||||
pub fn sign<Hash>(&self, m: &[u8]) -> DSASignature<$base>
|
pub fn sign<Hash>(&self, m: &[u8]) -> DSASignature<$base>
|
||||||
where
|
where
|
||||||
Hash: BlockInput + Clone + Default + Digest + FixedOutput + Input + Reset,
|
Hash: BlockInput + Clone + Default + Digest + FixedOutput + Input + Reset,
|
||||||
|
|||||||
@@ -8,11 +8,14 @@ use hmac::{Hmac,Mac};
|
|||||||
use simple_asn1::{ASN1Block,ASN1Class,ASN1DecodeErr,ASN1EncodeErr,FromASN1,ToASN1};
|
use simple_asn1::{ASN1Block,ASN1Class,ASN1DecodeErr,ASN1EncodeErr,FromASN1,ToASN1};
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
|
|
||||||
|
/// An ECDSA public key for the given curve.
|
||||||
#[derive(Debug,PartialEq)]
|
#[derive(Debug,PartialEq)]
|
||||||
pub struct ECCPublicKey<Curve: EllipticCurve> {
|
pub struct ECCPublicKey<Curve: EllipticCurve> {
|
||||||
pub(crate) q: Point<Curve>
|
pub(crate) q: Point<Curve>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A generic ECDSA public key, when you're not sure which curve you're
|
||||||
|
/// going to get.
|
||||||
pub enum ECDSAPublic {
|
pub enum ECDSAPublic {
|
||||||
P192(ECCPublicKey<P192>),
|
P192(ECCPublicKey<P192>),
|
||||||
P224(ECCPublicKey<P224>),
|
P224(ECCPublicKey<P224>),
|
||||||
@@ -21,6 +24,8 @@ pub enum ECDSAPublic {
|
|||||||
P521(ECCPublicKey<P521>),
|
P521(ECCPublicKey<P521>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An error that can occur when encoding an ECDSA public key as an ASN.1
|
||||||
|
/// object.
|
||||||
pub enum ECDSAEncodeErr {
|
pub enum ECDSAEncodeErr {
|
||||||
ASN1EncodeErr(ASN1EncodeErr),
|
ASN1EncodeErr(ASN1EncodeErr),
|
||||||
XValueNegative, YValueNegative
|
XValueNegative, YValueNegative
|
||||||
@@ -32,6 +37,8 @@ impl From<ASN1EncodeErr> for ECDSAEncodeErr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An error that can occur when decoding an ECDSA public key from an
|
||||||
|
/// ASN.1 blob.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ECDSADecodeErr {
|
pub enum ECDSADecodeErr {
|
||||||
ASN1DecodeErr(ASN1DecodeErr),
|
ASN1DecodeErr(ASN1DecodeErr),
|
||||||
@@ -50,11 +57,14 @@ macro_rules! public_impl {
|
|||||||
($curve: ident, $un: ident, $si: ident) => {
|
($curve: ident, $un: ident, $si: ident) => {
|
||||||
impl ECCPublicKey<$curve>
|
impl ECCPublicKey<$curve>
|
||||||
{
|
{
|
||||||
|
/// Generate a new public key object from the given public point.
|
||||||
pub fn new(q: Point<$curve>) -> ECCPublicKey<$curve>
|
pub fn new(q: Point<$curve>) -> ECCPublicKey<$curve>
|
||||||
{
|
{
|
||||||
ECCPublicKey{ q }
|
ECCPublicKey{ q }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if the given message matches the given signature,
|
||||||
|
/// assuming the provided hash function.
|
||||||
pub fn verify<Hash>(&self, m: &[u8], sig: &DSASignature<$un>) -> bool
|
pub fn verify<Hash>(&self, m: &[u8], sig: &DSASignature<$un>) -> bool
|
||||||
where
|
where
|
||||||
Hash: BlockInput + Clone + Default + Digest + FixedOutput + Input + Reset,
|
Hash: BlockInput + Clone + Default + Digest + FixedOutput + Input + Reset,
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
use cryptonum::signed::*;
|
|
||||||
use cryptonum::unsigned::*;
|
use cryptonum::unsigned::*;
|
||||||
use ecdsa::{ECDSAPair,ECDSAPublic,ECCPublicKey,ECDSAPrivate,ECCPrivateKey};
|
use ecdsa::{ECDSAPair,ECDSAPublic,ECCPublicKey,ECDSAPrivate,ECCPrivateKey};
|
||||||
use ecdsa::curve::{P256,P384,P521};
|
use ecdsa::{EllipticCurve,P256,P384,P521};
|
||||||
use ecdsa::point::Point;
|
|
||||||
use std::io::{Read,Write};
|
use std::io::{Read,Write};
|
||||||
use ssh::errors::{SSHKeyParseError,SSHKeyRenderError};
|
use ssh::errors::{SSHKeyParseError,SSHKeyRenderError};
|
||||||
use ssh::frame::*;
|
use ssh::frame::*;
|
||||||
@@ -33,7 +31,7 @@ impl SSHKey for ECDSAPair {
|
|||||||
}
|
}
|
||||||
let x = U256::from_bytes(&val[1..33]);
|
let x = U256::from_bytes(&val[1..33]);
|
||||||
let y = U256::from_bytes(&val[33..]);
|
let y = U256::from_bytes(&val[33..]);
|
||||||
let p = Point::<P256>{ x: I256::from(x), y: I256::from(y) };
|
let p = P256::new_point(x, y);
|
||||||
let pbl = ECCPublicKey::<P256>::new(p);
|
let pbl = ECCPublicKey::<P256>::new(p);
|
||||||
Ok(ECDSAPublic::P256(pbl))
|
Ok(ECDSAPublic::P256(pbl))
|
||||||
}
|
}
|
||||||
@@ -44,7 +42,7 @@ impl SSHKey for ECDSAPair {
|
|||||||
}
|
}
|
||||||
let x = U384::from_bytes(&val[1..49]);
|
let x = U384::from_bytes(&val[1..49]);
|
||||||
let y = U384::from_bytes(&val[49..]);
|
let y = U384::from_bytes(&val[49..]);
|
||||||
let p = Point::<P384>{ x: I384::from(x), y: I384::from(y) };
|
let p = P384::new_point(x, y);
|
||||||
let pbl = ECCPublicKey::<P384>::new(p);
|
let pbl = ECCPublicKey::<P384>::new(p);
|
||||||
Ok(ECDSAPublic::P384(pbl))
|
Ok(ECDSAPublic::P384(pbl))
|
||||||
}
|
}
|
||||||
@@ -55,7 +53,7 @@ impl SSHKey for ECDSAPair {
|
|||||||
}
|
}
|
||||||
let x = U576::from_bytes(&val[1..67]);
|
let x = U576::from_bytes(&val[1..67]);
|
||||||
let y = U576::from_bytes(&val[67..]);
|
let y = U576::from_bytes(&val[67..]);
|
||||||
let p = Point::<P521>{ x: I576::from(x), y: I576::from(y) };
|
let p = P521::new_point(x, y);
|
||||||
let pbl = ECCPublicKey::<P521>::new(p);
|
let pbl = ECCPublicKey::<P521>::new(p);
|
||||||
Ok(ECDSAPublic::P521(pbl))
|
Ok(ECDSAPublic::P521(pbl))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use cryptonum::unsigned::{U3072,U2048,U1024,U256,U192};
|
|||||||
use dsa::{DSAPublic,DSAPublicKey,DSAParameters};
|
use dsa::{DSAPublic,DSAPublicKey,DSAParameters};
|
||||||
use dsa::{L3072N256,L2048N256,L2048N224,L1024N160};
|
use dsa::{L3072N256,L2048N256,L2048N224,L1024N160};
|
||||||
use ecdsa::{ECDSAEncodeErr,ECDSAPublic,ECCPublicKey};
|
use ecdsa::{ECDSAEncodeErr,ECDSAPublic,ECCPublicKey};
|
||||||
use ecdsa::curve::{P192,P224,P256,P384,P521};
|
use ecdsa::{P192,P224,P256,P384,P521};
|
||||||
use num::BigUint;
|
use num::BigUint;
|
||||||
use rsa::RSAPublic;
|
use rsa::RSAPublic;
|
||||||
use simple_asn1::{ASN1Block,ASN1Class,ASN1EncodeErr,FromASN1,OID,ToASN1,
|
use simple_asn1::{ASN1Block,ASN1Class,ASN1EncodeErr,FromASN1,OID,ToASN1,
|
||||||
|
|||||||
Reference in New Issue
Block a user