aboutsummaryrefslogtreecommitdiff
path: root/xmpp-vala/src/module/xep/0300_cryptographic_hashes.vala
blob: 00f9e2eed0f6b80f3af3782ba12818a4044a9e4e (plain) (blame)
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
using GLib;
using Gee;

namespace Xmpp.Xep.CryptographicHashes {
    public const string NS_URI = "urn:xmpp:hashes:2";

    public enum HashCmp {
        Match,
        Mismatch,
        None,
    }

    public class Hash {
        public string algo;
        // hash encoded in Base64
        public string val;

        public static string hash_name(ChecksumType type) {
            switch(type) {
                case ChecksumType.MD5:
                    return "md5";
                case ChecksumType.SHA1:
                    return "sha-1";
                case ChecksumType.SHA256:
                    return "sha-256";
                case ChecksumType.SHA384:
                    return "sha-384";
                case ChecksumType.SHA512:
                    return "sha-512";
            }
            return "(null)";
        }

        public static ChecksumType? supported_hash(string hash) {
            switch (hash) {
                case "sha-1":
                    return ChecksumType.SHA1;
                case "sha-256":
                    return ChecksumType.SHA256;
                case "sha-384":
                    return ChecksumType.SHA384;
                case "sha-512":
                    return ChecksumType.SHA512;
            }
            return null;
        }

        public Hash.from_data(GLib.ChecksumType type, uint8[] data) {
            GLib.Checksum checksum = new GLib.Checksum(type);
            checksum.update(data, data.length);
            // 64 * 8 = 512 (sha-512 is the longest hash variant)
            uint8[] digest = new uint8[64];
            size_t length = digest.length;
            checksum.get_digest(digest, ref length);
            this.algo = hash_name(type);
            this.val = GLib.Base64.encode(digest[0:length]);
        }

        public HashCmp compare(Hash other) {
            if (this.algo != other.algo) {
                return HashCmp.None;
            }
            if (this.val == other.val) {
                return HashCmp.Match;
            } else {
                return HashCmp.Mismatch;
            }
        }

        public StanzaNode to_stanza_node() {
            return new StanzaNode.build("hash", NS_URI).add_self_xmlns()
                    .put_attribute("algo", this.algo)
                    .put_node(new StanzaNode.text(this.val));
        }

        public Hash.from_stanza_node(StanzaNode node) {
            this.algo = node.get_attribute("algo");
            this.val = node.get_string_content();
        }
    }

    public class Hashes {
        public Gee.List<Hash> hashes = new ArrayList<Hash>();

        public Gee.List<ChecksumType> supported_hashes() {
            Gee.List<ChecksumType> supported = new ArrayList<ChecksumType>();
            foreach (Hash hash in this.hashes) {
                ChecksumType? hash_type = Hash.supported_hash(hash.algo);
                if (hash_type != null) {
                    supported.add(hash_type);
                }
            }
            return supported;
        }

        public Hashes.from_data(Gee.List<ChecksumType> types, uint8[] data) {
            foreach (ChecksumType type in types) {
                this.hashes.add(new Hash.from_data(type, data));
            }
        }

        public HashCmp compare(Hashes other) {
            HashCmp cmp = HashCmp.None;
            foreach (Hash this_hash in this.hashes) {
                foreach (Hash other_hash in other.hashes) {
                    switch (this_hash.compare(other_hash)) {
                        case HashCmp.Mismatch:
                            return HashCmp.Mismatch;
                        case HashCmp.Match:
                            cmp = HashCmp.Match;
                            break;
                        case HashCmp.None:
                            continue;
                    }
                }
            }
            return cmp;
        }

        public Gee.List<StanzaNode> to_stanza_nodes() {
            Gee.List<StanzaNode> nodes = new ArrayList<StanzaNode>();
            foreach (Hash hash in this.hashes) {
                nodes.add(hash.to_stanza_node());
            }
            return nodes;
        }

        public Hashes.from_stanza_subnodes(StanzaNode node) {
            Gee.List<StanzaNode> subnodes = node.get_subnodes("hash", NS_URI);
            this.hashes = new ArrayList<Hash>();
            foreach (StanzaNode subnode in subnodes) {
                this.hashes.add(new Hash.from_stanza_node(subnode));
            }
        }
    }
}