aboutsummaryrefslogtreecommitdiff
path: root/crypto-vala/src/cipher_openssl.vala
blob: 75a0ef9abf8b3a8eddc7677726aeeb5515c7e06c (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
148
149
150
151
152
153
154
155
156
namespace Crypto {

internal class SymmetricCipherEncryptionOpenssl : SymmetricCipher, SymmetricCipherEncryption, Object {
    private OpenSSL.EVP.CipherContext? cipher;
    private bool is_finalized = false;

    public SymmetricCipherEncryptionOpenssl(string algo_name) throws Error {
        if (algo_name != "AES-GCM") throw new Error.ILLEGAL_ARGUMENTS(@"The algorithm $algo_name is not supported");
        cipher = new OpenSSL.EVP.CipherContext();
    }

    public void set_key(uint8[] key) throws Error {
        unowned OpenSSL.EVP.Cipher algo;
        if (key.length == 16) {
            algo = OpenSSL.EVP.aes_128_gcm();
        } else if (key.length == 32) {
            algo = OpenSSL.EVP.aes_256_gcm();
        } else {
            throw new Crypto.Error.ILLEGAL_ARGUMENTS("key length must be 16 or 32 for AES-GCM");
        }
        try {
            cipher.encrypt_init(algo, null, null);
            cipher.encrypt_init(null, key, null);
        } catch (GLib.Error e) {
            throw new Crypto.Error.OPENSSL(e.message);
        }
    }

    public void set_iv(uint8[] iv) throws Error {
        if (iv.length != 12) {
            cipher.ctrl(OpenSSL.EVP.Control.GCM_SET_IVLEN, iv.length, null);
        }
        try {
            cipher.encrypt_init(null, null, iv);
        } catch (GLib.Error e) {
            throw new Crypto.Error.OPENSSL(e.message);
        }
    }

    public void reset() throws Error {
        throw new Crypto.Error.ILLEGAL_ARGUMENTS("can't reset OpenSSL cipher context");
    }

    public uint8[] get_tag(size_t taglen) throws Error {
        uint8[] tag = new uint8[taglen];
        if (!is_finalized) {
            uint8[] empty = new uint8[0];
            int empty_len = 0;
            try {
                cipher.encrypt_final(empty, out empty_len);
            } catch (GLib.Error e) {
                throw new Crypto.Error.OPENSSL(e.message);
            }
            if (empty_len != 0) {
                throw new Crypto.Error.ILLEGAL_ARGUMENTS("get_tag called on a stream with remaining data");
            }
        }
        if (cipher.ctrl(OpenSSL.EVP.Control.GCM_GET_TAG, (int)taglen, tag) != 1) {
            openssl_error();
        }
        return tag;
    }

    public int encrypt(uint8[] output, uint8[] input) throws Error {
        int output_length = output.length;
        if (is_finalized) {
            throw new Crypto.Error.ILLEGAL_ARGUMENTS("encrypt called after get_tag");
        }
        try {
            cipher.encrypt_update(output, out output_length, input);
        } catch (GLib.Error e) {
            throw new Crypto.Error.OPENSSL(e.message);
        }
        return output_length;
    }
}

internal class SymmetricCipherDecryptionOpenssl : SymmetricCipher, SymmetricCipherDecryption, Object {
    private OpenSSL.EVP.CipherContext? cipher;
    private bool is_finalized = false;

    public SymmetricCipherDecryptionOpenssl(string algo_name) throws Error {
        if (algo_name != "AES-GCM") throw new Error.ILLEGAL_ARGUMENTS(@"The algorithm $algo_name is not supported");
        cipher = new OpenSSL.EVP.CipherContext();
        try {
            cipher.decrypt_init(OpenSSL.EVP.aes_128_gcm(), null, null);
        } catch (GLib.Error e) {
            throw new Crypto.Error.OPENSSL(e.message);
        }
    }

    public void set_key(uint8[] key) throws Error {
        unowned OpenSSL.EVP.Cipher algo;
        if (key.length == 16) {
            algo = OpenSSL.EVP.aes_128_gcm();
        } else if (key.length == 32) {
            algo = OpenSSL.EVP.aes_256_gcm();
        } else {
            throw new Crypto.Error.ILLEGAL_ARGUMENTS("key length must be 16 or 32 for AES-GCM");
        }
        try {
            cipher.decrypt_init(algo, null, null);
            cipher.decrypt_init(null, key, null);
        } catch (GLib.Error e) {
            throw new Crypto.Error.OPENSSL(e.message);
        }
    }

    public void set_iv(uint8[] iv) throws Error {
        if (iv.length != 12) {
            cipher.ctrl(OpenSSL.EVP.Control.GCM_SET_IVLEN, iv.length, null);
        }
        try {
            cipher.decrypt_init(null, null, iv);
        } catch (GLib.Error e) {
            throw new Crypto.Error.OPENSSL(e.message);
        }
    }

    public void reset() throws Error {
        throw new Crypto.Error.ILLEGAL_ARGUMENTS("can't reset OpenSSL cipher context");
    }

    public void check_tag(uint8[] tag) throws Error {
        if (cipher.ctrl(OpenSSL.EVP.Control.GCM_SET_TAG, tag.length, tag) != 1) {
            openssl_error();
        }
        if (!is_finalized) {
            uint8[] empty = new uint8[0];
            int empty_len = 0;
            try {
                cipher.decrypt_final(empty, out empty_len);
            } catch (GLib.Error e) {
                throw new Crypto.Error.OPENSSL(e.message);
            }
            if (empty_len != 0) {
                throw new Crypto.Error.ILLEGAL_ARGUMENTS("check_tag called on a stream with remaining data");
            }
        }
    }

    public int decrypt(uint8[] output, uint8[] input) throws Error {
        if (is_finalized) {
            throw new Crypto.Error.ILLEGAL_ARGUMENTS("decrypt called after check_tag");
        }
        int output_length = output.length;
        try {
            cipher.decrypt_update(output, out output_length, input);
        } catch (GLib.Error e) {
            throw new Crypto.Error.OPENSSL(e.message);
        }
        return output_length;
    }
}

}