xref: /freebsd/crypto/openssh/ed25519-openssl.c (revision 2574974648c68c738aec3ff96644d888d7913a37)
1 /* $OpenBSD: ed25519-openssl.c,v 1.1 2025/10/30 20:49:10 djm Exp $ */
2 /*
3  * Copyright (c) 2025 OpenSSH
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 /*
19  * OpenSSL-based implementation of Ed25519 crypto_sign API
20  * Alternative to the internal SUPERCOP-based implementation in ed25519.c
21  */
22 
23 #include "includes.h"
24 
25 #ifdef OPENSSL_HAS_ED25519
26 
27 #include <sys/types.h>
28 
29 #include <string.h>
30 #include <stdint.h>
31 #include <limits.h>
32 
33 #include <openssl/evp.h>
34 
35 #include "crypto_api.h"
36 #include "log.h"
37 
38 #if crypto_sign_ed25519_SECRETKEYBYTES <= crypto_sign_ed25519_PUBLICKEYBYTES
39 #error "crypto_sign_ed25519_SECRETKEYBYTES < crypto_sign_ed25519_PUBLICKEYBYTES"
40 #endif
41 
42 #define SSH_ED25519_RAW_SECRET_KEY_LEN \
43     (crypto_sign_ed25519_SECRETKEYBYTES - crypto_sign_ed25519_PUBLICKEYBYTES)
44 
45 int
crypto_sign_ed25519_keypair(unsigned char * pk,unsigned char * sk)46 crypto_sign_ed25519_keypair(unsigned char *pk, unsigned char *sk)
47 {
48 	EVP_PKEY_CTX *ctx = NULL;
49 	EVP_PKEY *pkey = NULL;
50 	size_t pklen, sklen;
51 	int ret = -1;
52 
53 	if ((ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, NULL)) == NULL) {
54 		debug3_f("EVP_PKEY_CTX_new_id failed");
55 		goto out;
56 	}
57 	if (EVP_PKEY_keygen_init(ctx) <= 0) {
58 		debug3_f("EVP_PKEY_keygen_init failed");
59 		goto out;
60 	}
61 	if (EVP_PKEY_keygen(ctx, &pkey) <= 0) {
62 		debug3_f("EVP_PKEY_keygen failed");
63 		goto out;
64 	}
65 
66 	/* Extract public key */
67 	pklen = crypto_sign_ed25519_PUBLICKEYBYTES;
68 	if (!EVP_PKEY_get_raw_public_key(pkey, pk, &pklen)) {
69 		debug3_f("EVP_PKEY_get_raw_public_key failed");
70 		goto out;
71 	}
72 	if (pklen != crypto_sign_ed25519_PUBLICKEYBYTES) {
73 		debug3_f("public key length mismatch: %zu", pklen);
74 		goto out;
75 	}
76 
77 	sklen = SSH_ED25519_RAW_SECRET_KEY_LEN;
78 	/* Extract private key (32 bytes seed) */
79 	if (!EVP_PKEY_get_raw_private_key(pkey, sk, &sklen)) {
80 		debug3_f("EVP_PKEY_get_raw_private_key failed");
81 		goto out;
82 	}
83 	if (sklen != SSH_ED25519_RAW_SECRET_KEY_LEN) {
84 		debug3_f("private key length mismatch: %zu", sklen);
85 		goto out;
86 	}
87 
88 	/* Append public key to secret key (SUPERCOP format compatibility) */
89 	memcpy(sk + sklen, pk, crypto_sign_ed25519_PUBLICKEYBYTES);
90 
91 	ret = 0;
92 out:
93 	EVP_PKEY_free(pkey);
94 	EVP_PKEY_CTX_free(ctx);
95 	return ret;
96 }
97 
98 int
crypto_sign_ed25519(unsigned char * sm,unsigned long long * smlen,const unsigned char * m,unsigned long long mlen,const unsigned char * sk)99 crypto_sign_ed25519(unsigned char *sm, unsigned long long *smlen,
100     const unsigned char *m, unsigned long long mlen,
101     const unsigned char *sk)
102 {
103 	EVP_PKEY *pkey = NULL;
104 	EVP_MD_CTX *mdctx = NULL;
105 	size_t siglen;
106 	int ret = -1;
107 
108 	/* Create EVP_PKEY from secret key (first 32 bytes are the seed) */
109 	if ((pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL,
110 	    sk, SSH_ED25519_RAW_SECRET_KEY_LEN)) == NULL) {
111 		debug3_f("EVP_PKEY_new_raw_private_key failed");
112 		goto out;
113 	}
114 
115 	/* Sign the message */
116 	if ((mdctx = EVP_MD_CTX_new()) == NULL) {
117 		debug3_f("EVP_MD_CTX_new failed");
118 		goto out;
119 	}
120 	if (EVP_DigestSignInit(mdctx, NULL, NULL, NULL, pkey) != 1) {
121 		debug3_f("EVP_DigestSignInit failed");
122 		goto out;
123 	}
124 	siglen = crypto_sign_ed25519_BYTES;
125 	if (EVP_DigestSign(mdctx, sm, &siglen, m, mlen) != 1) {
126 		debug3_f("EVP_DigestSign failed");
127 		goto out;
128 	}
129 	if (siglen != crypto_sign_ed25519_BYTES) {
130 		debug3_f("signature length mismatch: %zu", siglen);
131 		goto out;
132 	}
133 
134 	/* Append message after signature (SUPERCOP format) */
135 	if (mlen > ULLONG_MAX - siglen) {
136 		debug3_f("message length overflow: siglen=%zu mlen=%llu",
137 		    siglen, mlen);
138 		goto out;
139 	}
140 	memmove(sm + siglen, m, mlen);
141 	*smlen = siglen + mlen;
142 
143 	ret = 0;
144 out:
145 	EVP_MD_CTX_free(mdctx);
146 	EVP_PKEY_free(pkey);
147 	return ret;
148 }
149 
150 int
crypto_sign_ed25519_open(unsigned char * m,unsigned long long * mlen,const unsigned char * sm,unsigned long long smlen,const unsigned char * pk)151 crypto_sign_ed25519_open(unsigned char *m, unsigned long long *mlen,
152     const unsigned char *sm, unsigned long long smlen,
153     const unsigned char *pk)
154 {
155 	EVP_PKEY *pkey = NULL;
156 	EVP_MD_CTX *mdctx = NULL;
157 	int ret = -1;
158 	const unsigned char *msg;
159 	size_t msglen;
160 
161 	if (smlen < crypto_sign_ed25519_BYTES) {
162 		debug3_f("signed message bad length: %llu", smlen);
163 		return -1;
164 	}
165 	/* Signature is first crypto_sign_ed25519_BYTES, message follows */
166 	msg = sm + crypto_sign_ed25519_BYTES;
167 	msglen = smlen - crypto_sign_ed25519_BYTES;
168 
169 	/* Make sure the message buffer is big enough. */
170 	if (*mlen < msglen) {
171 		debug_f("message bad length: %llu", *mlen);
172 		return -1;
173 	}
174 
175 	/* Create EVP_PKEY from public key */
176 	if ((pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL,
177 	    pk, crypto_sign_ed25519_PUBLICKEYBYTES)) == NULL) {
178 		debug3_f("EVP_PKEY_new_raw_public_key failed");
179 		goto out;
180 	}
181 
182 	if ((mdctx = EVP_MD_CTX_new()) == NULL) {
183 		debug3_f("EVP_MD_CTX_new failed");
184 		goto out;
185 	}
186 	if (EVP_DigestVerifyInit(mdctx, NULL, NULL, NULL, pkey) <= 0) {
187 		debug3_f("EVP_DigestVerifyInit failed");
188 		goto out;
189 	}
190 	if (EVP_DigestVerify(mdctx, sm, crypto_sign_ed25519_BYTES,
191 	    msg, msglen) != 1) {
192 		debug3_f("EVP_DigestVerify failed");
193 		goto out;
194 	}
195 
196 	/* Copy message out */
197 	*mlen = msglen;
198 	memmove(m, msg, msglen);
199 
200 	ret = 0;
201 out:
202 	EVP_MD_CTX_free(mdctx);
203 	EVP_PKEY_free(pkey);
204 	return ret;
205 }
206 
207 #endif /* OPENSSL_HAS_ED25519 */
208