summaryrefslogtreecommitdiffstats
path: root/openpgp/src/crypto/backend/botan/ecdh.rs
blob: 87eab7571f92f75711646475bdeca25d8cd75ff5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
//! Elliptic Curve Diffie-Hellman.

use botan::{
    RandomNumberGenerator,
    Privkey,
};

use crate::{
    Error,
    Result,
};
use crate::crypto::SessionKey;
use crate::crypto::ecdh::{encrypt_wrap, decrypt_unwrap};
use crate::crypto::mem::Protected;
use crate::crypto::mpi::{
    MPI,
    PublicKey, SecretKeyMaterial, Ciphertext};
use crate::packet::{key, Key};
use crate::types::Curve;

/// Wraps a session key using Elliptic Curve Diffie-Hellman.
#[allow(non_snake_case)]
pub fn encrypt<R>(recipient: &Key<key::PublicParts, R>,
                  session_key: &SessionKey)
    -> Result<Ciphertext>
    where R: key::KeyRole
{
    let mut rng = RandomNumberGenerator::new_userspace()?;

    if let PublicKey::ECDH {
        ref curve, ref q,..
    } = recipient.mpis() {
        match curve {
            Curve::Cv25519 => {
                // Obtain the recipient public key R
                let R = &q.decode_point(curve)?.0;

                // Generate an ephemeral key pair {v, V=vG}
                let v = Privkey::create("Curve25519", "", &mut rng)?;
                let V = v.pubkey()?.get_x25519_key()?;

                // Compute the shared point S = vR;
                let S: Protected = v.agree(&R, 32, b"", "Raw")?.into();

                encrypt_wrap(recipient, session_key,
                             MPI::new_compressed_point(&V),
                             &S)
            },

            // N/A
            Curve::Unknown(_) if ! curve.is_brainpoolp384() =>
                Err(Error::UnsupportedEllipticCurve(curve.clone()).into()),
            Curve::Ed25519 =>
                Err(Error::UnsupportedEllipticCurve(curve.clone()).into()),

            Curve::NistP256 | Curve::NistP384 | Curve::NistP521 |
            Curve::BrainpoolP256 |
            Curve::Unknown(_) | // XXX: this is BrainpoolP384
            Curve::BrainpoolP512 => {
                // Obtain the recipient public key R
                let R = &q.value();

                // Generate an ephemeral key pair {v, V=vG}
                let field_size = curve.field_size()?;
                let v = Privkey::create("ECDH", curve.botan_name()?, &mut rng)?;
                let Vx = v.pubkey()?.get_field("public_x")?;
                let Vy = v.pubkey()?.get_field("public_y")?;

                // Compute the shared point S = vR;
                let S: Protected = v.agree(&R, 32, b"", "Raw")?.into();
                let Sx: Protected = S[..field_size].into();

                encrypt_wrap(recipient, session_key,
                             MPI::new_point(&Vx.to_bin()?, &Vy.to_bin()?,
                                            field_size * 8),
                             &Sx.into())
            }
        }
    } else {
        Err(Error::InvalidArgument("Expected an ECDHPublicKey".into()).into())
    }
}

/// Unwraps a session key using Elliptic Curve Diffie-Hellman.
#[allow(non_snake_case)]
pub fn decrypt<R>(recipient: &Key<key::PublicParts, R>,
                  recipient_sec: &SecretKeyMaterial,
                  ciphertext: &Ciphertext)
    -> Result<SessionKey>
    where R: key::KeyRole
{
    match (recipient.mpis(), recipient_sec, ciphertext) {
        (PublicKey::ECDH { ref curve, ..},
         SecretKeyMaterial::ECDH { ref scalar, },
         Ciphertext::ECDH { ref e, .. }) =>
        {
            let S: Protected = match curve {
                Curve::Cv25519 => {
                    // Get the public part V of the ephemeral key.
                    let V = e.decode_point(curve)?.0;

                    // Get our secret key.
                    let mut r = scalar.value_padded(32);

                    // Reverse the scalar.  See
                    // https://lists.gnupg.org/pipermail/gnupg-devel/2018-February/033437.html.
                    r.reverse();
                    let r = Privkey::load_x25519(&r)?;

                    // Compute the shared point S = rV = rvG, where (r, R)
                    // is the recipient's key pair.
                    r.agree(&V, 32, b"", "Raw")?.into()
                },


                // N/A
                Curve::Unknown(_) if ! curve.is_brainpoolp384() => return
                    Err(Error::UnsupportedEllipticCurve(curve.clone()).into()),
                Curve::Ed25519 => return
                    Err(Error::UnsupportedEllipticCurve(curve.clone()).into()),


                Curve::NistP256 | Curve::NistP384 | Curve::NistP521 |
                Curve::BrainpoolP256 |
                Curve::Unknown(_) | // XXX: this is BrainpoolP384
                Curve::BrainpoolP512 => {
                    // Get the public part V of the ephemeral key.
                    let V = &e.value();

                    // Get our secret key.
                    let r = Privkey::load_ecdh(
                        &botan::MPI::new_from_bytes(scalar.value())?,
                        curve.botan_name()?)?;

                    // Compute the shared point S = rV = rvG, where (r, R)
                    // is the recipient's key pair.
                    r.agree(V, curve.field_size()?, b"", "Raw")?.into()
                },
            };

            decrypt_unwrap(recipient, &S, ciphertext)
        }

        _ =>
            Err(Error::InvalidArgument("Expected an ECDHPublicKey".into()).into()),
    }
}