| 1 | module ecdsa |
| 2 | |
| 3 | // pubkey_from_bytes loads ECDSA Public Key from bytes array. |
| 4 | // The bytes of data should be a valid of ASN.1 DER serialized SubjectPublicKeyInfo structrue of RFC 5480. |
| 5 | // Otherwise, its should an error. |
| 6 | // Typically, you can load the bytes from pem formatted of ecdsa public key. |
| 7 | // |
| 8 | // Examples: |
| 9 | // ```codeblock |
| 10 | // import crypto.pem |
| 11 | // import crypto.ecdsa |
| 12 | // |
| 13 | // const pubkey_sample = '-----BEGIN PUBLIC KEY----- |
| 14 | // MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE+P3rhFkT1fXHYbY3CpcBdh6xTC74MQFx |
| 15 | // cftNVD3zEPVzo//OalIVatY162ksg8uRWBdvFFuHZ9OMVXkbjwWwhcXP7qmI9rOS |
| 16 | // LR3AGUldy+bBpV2nT306qCIwgUAMeOJP |
| 17 | // -----END PUBLIC KEY-----' |
| 18 | // |
| 19 | // block, _ := pem.decode(pubkey_sample) or { panic(err) } |
| 20 | // pubkey := ecdsa.pubkey_from_bytes(block.data)! |
| 21 | // ``` |
| 22 | pub fn pubkey_from_bytes(bytes []u8) !PublicKey { |
| 23 | if bytes.len == 0 { |
| 24 | return error('Invalid bytes') |
| 25 | } |
| 26 | mut pub_key := C.EVP_PKEY_new() |
| 27 | pub_key = C.d2i_PUBKEY(&pub_key, voidptr(&bytes.data), bytes.len) |
| 28 | if pub_key == 0 { |
| 29 | C.EVP_PKEY_free(pub_key) |
| 30 | return error('Error loading public key') |
| 31 | } |
| 32 | // Get the NID of this pubkey, and check if the pubkey object was |
| 33 | // have the correct NID of ec public key type, ie, NID_X9_62_id_ecPublicKey |
| 34 | nid := C.EVP_PKEY_base_id(pub_key) |
| 35 | if nid != nid_ec_publickey { |
| 36 | C.EVP_PKEY_free(pub_key) |
| 37 | return error('Get an nid of non ecPublicKey') |
| 38 | } |
| 39 | |
| 40 | // check the key |
| 41 | pctx := C.EVP_PKEY_CTX_new(pub_key, 0) |
| 42 | if pctx == 0 { |
| 43 | C.EVP_PKEY_CTX_free(pctx) |
| 44 | C.EVP_PKEY_free(pub_key) |
| 45 | return error('EVP_PKEY_CTX_new failed') |
| 46 | } |
| 47 | // performs public-only evpkey check |
| 48 | nck := C.EVP_PKEY_public_check(pctx) |
| 49 | if nck != 1 { |
| 50 | C.EVP_PKEY_CTX_free(pctx) |
| 51 | C.EVP_PKEY_free(pub_key) |
| 52 | return error('EVP_PKEY_check failed') |
| 53 | } |
| 54 | // Matching the supported group |
| 55 | gn := key_group_name(pub_key)! |
| 56 | // TODO: using shortname constant |
| 57 | if gn != 'secp256k1' && gn != 'secp384r1' && gn != 'secp521r1' && gn != 'prime256v1' { |
| 58 | C.EVP_PKEY_CTX_free(pctx) |
| 59 | C.EVP_PKEY_free(pub_key) |
| 60 | return error('Unsupported group') |
| 61 | } |
| 62 | // Cleans up |
| 63 | C.EVP_PKEY_CTX_free(pctx) |
| 64 | |
| 65 | // Its OK to return |
| 66 | return PublicKey{ |
| 67 | evpkey: pub_key |
| 68 | } |
| 69 | } |
| 70 | |
| 71 | // bytes gets the bytes of public key. |
| 72 | pub fn (pbk PublicKey) bytes() ![]u8 { |
| 73 | mut ppub := &u8(unsafe { nil }) |
| 74 | n := C.EVP_PKEY_get1_encoded_public_key(pbk.evpkey, &ppub) |
| 75 | if n <= 0 { |
| 76 | C.OPENSSL_free(voidptr(ppub)) |
| 77 | return error('EVP_PKEY_get1_encoded_public_key failed') |
| 78 | } |
| 79 | out := unsafe { ppub.vbytes(int(n)).clone() } |
| 80 | // ppub should be freed by calling `OPENSSL_free` or memleak happens. |
| 81 | C.OPENSSL_free(voidptr(ppub)) |
| 82 | |
| 83 | return out |
| 84 | } |
| 85 | |
| 86 | // pubkey_from_string loads a PublicKey from valid PEM-formatted string in s. |
| 87 | pub fn pubkey_from_string(s string) !PublicKey { |
| 88 | if s.len == 0 { |
| 89 | return error('Null length string was not allowed') |
| 90 | } |
| 91 | mut evpkey := C.EVP_PKEY_new() |
| 92 | bo := C.BIO_new(C.BIO_s_mem()) |
| 93 | if bo == 0 { |
| 94 | C.EVP_PKEY_free(evpkey) |
| 95 | C.BIO_free_all(bo) |
| 96 | return error('Failed to create BIO_new') |
| 97 | } |
| 98 | n := C.BIO_write(bo, s.str, s.len) |
| 99 | if n <= 0 { |
| 100 | C.EVP_PKEY_free(evpkey) |
| 101 | C.BIO_free_all(bo) |
| 102 | return error('BIO_write failed') |
| 103 | } |
| 104 | evpkey = C.PEM_read_bio_PUBKEY(bo, &evpkey, 0, 0) |
| 105 | if evpkey == 0 { |
| 106 | C.BIO_free_all(bo) |
| 107 | C.EVP_PKEY_free(evpkey) |
| 108 | return error('Error loading key') |
| 109 | } |
| 110 | // Get the NID of this key, and check if the key object was |
| 111 | // have the correct NID of ec public key type, ie, NID_X9_62_id_ecPublicKey |
| 112 | nid := C.EVP_PKEY_base_id(evpkey) |
| 113 | if nid != nid_ec_publickey { |
| 114 | C.BIO_free_all(bo) |
| 115 | C.EVP_PKEY_free(evpkey) |
| 116 | return error('Get an nid of non ecPublicKey') |
| 117 | } |
| 118 | // check the key |
| 119 | pctx := C.EVP_PKEY_CTX_new(evpkey, 0) |
| 120 | if pctx == 0 { |
| 121 | C.BIO_free_all(bo) |
| 122 | C.EVP_PKEY_CTX_free(pctx) |
| 123 | C.EVP_PKEY_free(evpkey) |
| 124 | return error('EVP_PKEY_CTX_new failed') |
| 125 | } |
| 126 | // performs public-only evpkey check |
| 127 | nck := C.EVP_PKEY_public_check(pctx) |
| 128 | if nck != 1 { |
| 129 | C.BIO_free_all(bo) |
| 130 | C.EVP_PKEY_CTX_free(pctx) |
| 131 | C.EVP_PKEY_free(evpkey) |
| 132 | return error('EVP_PKEY_check failed') |
| 133 | } |
| 134 | // Matching the supported group |
| 135 | gn := key_group_name(evpkey)! |
| 136 | // TODO: using shortname constant |
| 137 | if gn != 'secp256k1' && gn != 'secp384r1' && gn != 'secp521r1' && gn != 'prime256v1' { |
| 138 | C.BIO_free_all(bo) |
| 139 | C.EVP_PKEY_CTX_free(pctx) |
| 140 | C.EVP_PKEY_free(evpkey) |
| 141 | return error('Unsupported group') |
| 142 | } |
| 143 | // Cleans up |
| 144 | C.BIO_free_all(bo) |
| 145 | C.EVP_PKEY_CTX_free(pctx) |
| 146 | |
| 147 | // Its OK to return |
| 148 | return PublicKey{ |
| 149 | evpkey: evpkey |
| 150 | } |
| 151 | } |
| 152 | |
| 153 | // privkey_from_string loads a PrivateKey from valid PEM-formatted string in s. |
| 154 | // Underlying wrapper support for old SECG and PKCS8 private key format, but this was not heavily tested. |
| 155 | // This routine does not support for the PKCS8 EncryptedPrivateKeyInfo format. |
| 156 | // See [ecdsa_seed_test.v](https://github.com/vlang/v/blob/master/vlib/crypto/ecdsa/example/ecdsa_seed_test.v) file |
| 157 | // for example of usage. |
| 158 | pub fn privkey_from_string(s string) !PrivateKey { |
| 159 | if s.len == 0 { |
| 160 | return error('null string was not allowed') |
| 161 | } |
| 162 | mut evpkey := C.EVP_PKEY_new() |
| 163 | bo := C.BIO_new(C.BIO_s_mem()) |
| 164 | if bo == 0 { |
| 165 | C.BIO_free_all(bo) |
| 166 | C.EVP_PKEY_free(evpkey) |
| 167 | return error('Failed to create BIO_new') |
| 168 | } |
| 169 | n := C.BIO_write(bo, s.str, s.len) |
| 170 | if n <= 0 { |
| 171 | C.BIO_free_all(bo) |
| 172 | C.EVP_PKEY_free(evpkey) |
| 173 | return error('BIO_write failed') |
| 174 | } |
| 175 | evpkey = C.PEM_read_bio_PrivateKey(bo, &evpkey, 0, 0) |
| 176 | if evpkey == 0 { |
| 177 | C.BIO_free_all(bo) |
| 178 | C.EVP_PKEY_free(evpkey) |
| 179 | return error('Error loading key') |
| 180 | } |
| 181 | |
| 182 | // Get the NID of this key, and check if the key object was |
| 183 | // have the correct NID of ec public key type, ie, NID_X9_62_id_ecPublicKey |
| 184 | nid := C.EVP_PKEY_base_id(evpkey) |
| 185 | if nid != nid_ec_publickey { |
| 186 | C.BIO_free_all(bo) |
| 187 | C.EVP_PKEY_free(evpkey) |
| 188 | return error('Get an nid of non ecPublicKey') |
| 189 | } |
| 190 | pctx := C.EVP_PKEY_CTX_new(evpkey, 0) |
| 191 | if pctx == 0 { |
| 192 | C.BIO_free_all(bo) |
| 193 | C.EVP_PKEY_CTX_free(pctx) |
| 194 | C.EVP_PKEY_free(evpkey) |
| 195 | return error('EVP_PKEY_CTX_new failed') |
| 196 | } |
| 197 | // performs evpkey check |
| 198 | nck := C.EVP_PKEY_check(pctx) |
| 199 | if nck != 1 { |
| 200 | C.BIO_free_all(bo) |
| 201 | C.EVP_PKEY_CTX_free(pctx) |
| 202 | C.EVP_PKEY_free(evpkey) |
| 203 | return error('EVP_PKEY_check failed') |
| 204 | } |
| 205 | // Matching the supported group |
| 206 | gn := key_group_name(evpkey)! |
| 207 | // TODO: using shortname constant |
| 208 | if gn != 'secp256k1' && gn != 'secp384r1' && gn != 'secp521r1' && gn != 'prime256v1' { |
| 209 | C.BIO_free_all(bo) |
| 210 | C.EVP_PKEY_CTX_free(pctx) |
| 211 | C.EVP_PKEY_free(evpkey) |
| 212 | return error('Unsupported group') |
| 213 | } |
| 214 | // Cleans up |
| 215 | C.BIO_free_all(bo) |
| 216 | C.EVP_PKEY_CTX_free(pctx) |
| 217 | |
| 218 | // Its OK to return |
| 219 | return PrivateKey{ |
| 220 | evpkey: evpkey |
| 221 | ks_flag: .fixed |
| 222 | } |
| 223 | } |
| 224 | |
| 225 | // evp_key_size get the key size of this ec key |
| 226 | fn evp_key_size(key &C.EVP_PKEY) !int { |
| 227 | num_bits := C.EVP_PKEY_get_bits(key) |
| 228 | key_size := (num_bits + 7) / 8 |
| 229 | |
| 230 | return key_size |
| 231 | } |
| 232 | |
| 233 | const default_groupname_size = 25 // short name commonly only take 10-15 length |
| 234 | |
| 235 | // key_group_name returns underlying group name of the key as a string. |
| 236 | fn key_group_name(key &C.EVP_PKEY) !string { |
| 237 | gname := []u8{len: default_groupname_size} |
| 238 | gname_len := usize(0) |
| 239 | mut s := C.EVP_PKEY_get_group_name(key, gname.data, u32(gname.len), &gname_len) |
| 240 | if s == 0 { |
| 241 | unsafe { gname.free() } |
| 242 | return error('fail to get group name') |
| 243 | } |
| 244 | group := gname[..int(gname_len)].bytestr() |
| 245 | unsafe { gname.free() } |
| 246 | return group |
| 247 | } |
| 248 | |