1 module libssh.key;
2 
3 import core.stdc..string : memcpy;
4 
5 import libssh.c_bindings.libssh;
6 import libssh.errors;
7 import libssh.utils;
8 
9 enum PublicKeyHashType : int {
10     SHA1 = ssh_publickey_hash_type.SSH_PUBLICKEY_HASH_SHA1,
11     MD5 = ssh_publickey_hash_type.SSH_PUBLICKEY_HASH_MD5,
12 }
13 
14 enum KeyComparePart : ssh_keycmp_e {
15     Public = ssh_keycmp_e.SSH_KEY_CMP_PUBLIC,
16     Private = ssh_keycmp_e.SSH_KEY_CMP_PRIVATE,
17 }
18 
19 enum KeyType : ssh_keytypes_e {
20     Unknown = ssh_keytypes_e.SSH_KEYTYPE_UNKNOWN,
21     DSS = ssh_keytypes_e.SSH_KEYTYPE_DSS,
22     RSA = ssh_keytypes_e.SSH_KEYTYPE_RSA,
23     RSA1 = ssh_keytypes_e.SSH_KEYTYPE_RSA1,
24     ECDSA = ssh_keytypes_e.SSH_KEYTYPE_ECDSA,
25     ED25519 = ssh_keytypes_e.SSH_KEYTYPE_ED25519,
26 }
27 
28 enum PublicKeyState : int {
29     Error = ssh_publickey_state_e.SSH_PUBLICKEY_STATE_ERROR,
30     None = ssh_publickey_state_e.SSH_PUBLICKEY_STATE_NONE,
31     Valid = ssh_publickey_state_e.SSH_PUBLICKEY_STATE_VALID,
32     Wrong = ssh_publickey_state_e.SSH_PUBLICKEY_STATE_WRONG,
33 }
34 
35 class SSHKey : Disposable {
36     alias AuthCallback = string delegate(string prompt, bool echo, bool verify);
37 
38     @property bool isPrivate() {
39         return ssh_key_is_private(this._key) == 0 ? false : true;
40     }
41 
42     @property bool isPublic() {
43         return ssh_key_is_public(this._key) == 0 ? false : true;
44     }
45 
46     @property KeyType keyType() {
47         return cast(KeyType) ssh_key_type(this._key);
48     }
49 
50     @property string ecdsaName() {
51         return fromStrZ(ssh_pki_key_ecdsa_name(this._key));
52     }
53 
54     ubyte[] getHash(PublicKeyHashType hashType) {
55         ubyte* hash;
56         size_t hashLength;
57         auto rc = ssh_get_publickey_hash(this._key, cast(ssh_publickey_hash_type) hashType, &hash, &hashLength);
58         checkForRCError(rc, rc);
59         scope(exit) ssh_clean_pubkey_hash(&hash);
60         
61         ubyte[] result = new ubyte[hashLength];
62         memcpy(result.ptr, hash, hashLength);
63         return result;
64     }
65 
66     void exportPrivateKeyToFile(string passPhrase, string fileName, AuthCallback authFn = null) {
67         auto rc = ssh_pki_export_privkey_file(this._key, toStrZ(passPhrase),
68             authFn is null ? null : &nativeAuthCallback, authFn.ptr, toStrZ(fileName));
69         checkForRCError(rc, rc);
70     }
71 
72     SSHKey exportPrivateKeyToPublicKey() {
73         ssh_key result;
74         auto rc = ssh_pki_export_privkey_to_pubkey(this._key, &result);
75         checkForRCError(rc, rc);
76         checkForNullError(result, rc);
77         return new SSHKey(result);
78     }
79 
80     string exportPrivateKeyToBase64() {
81         char* result;
82         auto rc = ssh_pki_export_pubkey_base64(this._key, &result);
83         checkForNullError(result, rc);
84         scope(exit) ssh_string_free_char(result);
85         checkForRCError(rc, rc);
86         return copyFromStrZ(result);
87     }
88 
89     override bool opEquals(Object o) {
90         auto b = cast(SSHKey) o;
91         if (b is null) {
92             return false;
93         }
94         return isKeysEqual(this, b, KeyComparePart.Private) &&
95             isKeysEqual(this, b, KeyComparePart.Public);
96     }
97 
98     static bool isKeysEqual(const SSHKey a, const SSHKey b, KeyComparePart comparePart) {
99         return ssh_key_cmp(a._key, b._key, comparePart) == 0 ? true : false;
100     }
101 
102     static KeyType keyTypeFromString(string name) {
103         return cast(KeyType) ssh_key_type_from_name(toStrZ(name));
104     }
105 
106     static string keyTypeToString(KeyType kt) {
107         return copyFromStrZ(ssh_key_type_to_char(kt));
108     }
109 
110     static SSHKey importPrivateKeyFromBase64(string b64, string passPhrase,
111             AuthCallback authFn = null) {
112         ssh_key result;
113         auto rc = ssh_pki_import_privkey_base64(toStrZ(b64), toStrZ(passPhrase),
114             authFn is null ? null : &nativeAuthCallback, authFn.ptr, &result);
115         checkForRCError(rc, rc);
116         checkForNullError(result, rc);
117         return new SSHKey(result);
118     }
119 
120     static SSHKey importPrivateKeyFromFile(string fileName, string passPhrase,
121         AuthCallback authFn = null) {
122         ssh_key result;
123         auto rc = ssh_pki_import_privkey_file(toStrZ(fileName), toStrZ(passPhrase),
124             authFn is null ? null : &nativeAuthCallback, authFn.ptr, &result);
125         checkForRCError(rc, rc);
126         checkForNullError(result, rc);
127         return new SSHKey(result);
128     }
129 
130     static SSHKey importPublicKeyFromBase64(string b64, KeyType keyType) {
131         ssh_key result;
132         auto rc = ssh_pki_import_pubkey_base64(toStrZ(b64), cast(ssh_keytypes_e) keyType,
133             &result);
134         checkForRCError(rc, rc);
135         checkForNullError(result, rc);
136         return new SSHKey(result);
137     }
138 
139     static SSHKey importPublicKeyFromFile(string fileName) {
140         ssh_key result;
141         auto rc = ssh_pki_import_pubkey_file(toStrZ(fileName), &result);
142         checkForRCError(rc, rc);
143         checkForNullError(result, rc);
144         return new SSHKey(result);
145     }
146 
147     static SSHKey generate(KeyType keyType, int bitsLength) {
148         ssh_key result;
149         auto rc = ssh_pki_generate(cast(ssh_keytypes_e) keyType, bitsLength, &result);
150         checkForRCError(rc, rc);
151         checkForNullError(result, rc);
152         return new SSHKey(result);
153     }
154 
155     override void dispose() {
156         this._dispose(false);
157     }
158 
159     this() {
160         auto key = ssh_key_new();
161         checkForNullError(key, "Error while creating ssh key");
162         this(key);
163     }
164     
165     ~this() {
166         this._dispose(true);
167     }
168     
169     package {
170         this(ssh_key key) {
171             this._key = key;
172         }
173         
174         ssh_key _key;
175     }
176     
177     private {
178         void _dispose(bool fromDtor) {
179             if (this._key !is null) {
180                 ssh_key_free(this._key);
181                 this._key = null;
182             }
183         }
184     }
185 }
186 
187 
188 private {
189     extern(C) int nativeAuthCallback(const char *prompt, char *buf, size_t len,
190             int echo, int verify, void *userdata) {
191         auto cb = cast(SSHKey.AuthCallback*) userdata;
192         
193         if (cb is null) {
194             return SSH_ERROR;
195         }
196         
197         try {
198             auto result = (*cb)(fromStrZ(prompt), echo == 0 ? false : true, 
199                 verify == 0 ? false : true);
200             if (result is null) {
201                 return SSH_ERROR;
202             }
203             
204             if (len < result.length + 1) {
205                 return SSH_ERROR;
206             }
207             
208             import core.stdc..string : memcpy;
209             memcpy(buf, result.ptr, result.length);
210             buf[result.length] = 0;
211             
212             return SSH_OK;
213         } catch (Exception) {
214             return SSH_ERROR;
215         }
216     }
217 }