Add certificate loading APIs with content-based format detection

pull/1147/head
Yosuke Shimizu 2026-08-03 16:05:01 +09:00 committed by John Safranek
parent 492e8cd65d
commit c2d169872e
10 changed files with 1052 additions and 53 deletions

3
.gitignore vendored
View File

@ -83,6 +83,9 @@ keys/*-ossh-*cert.pub
random-test.txt
random-test-result.txt
test.dat
# Scratch file the api tests write in the working directory. Removed on a
# clean run, left behind when one aborts.
ossh-cert-line.tmp
# test output
tests/*.test

View File

@ -20,6 +20,7 @@ EXTRA_DIST+= \
keys/pubkeys-rsa.txt keys/passwd.txt keys/ca-cert-ecc.der \
keys/ca-cert-ecc.pem keys/ca-key-ecc.der keys/ca-key-ecc.pem \
keys/server-cert.der keys/server-cert.pem \
keys/server-cert-ed25519.der keys/server-cert-ed25519.pem \
keys/fred-cert.der keys/fred-cert.pem \
keys/server-key.pem keys/fred-key.der keys/fred-key.pem \
keys/id_ecdsa keys/id_ecdsa.pub keys/id_rsa keys/id_rsa.pub \

View File

@ -34,6 +34,16 @@ openssl req -subj '/C=US/ST=Washington/L=Seattle/O=Eliptic/OU=ECC/CN=www.wolfssl
openssl x509 -req -in server-cert.csr -days 3650 -extfile "$CONFIG" -extensions v3_server -CA ca-cert-ecc.pem -CAkey ca-key-ecc.pem -text -out server-cert.pem -set_serial 8
openssl x509 -in server-cert.pem -outform DER -out server-cert.der
# renew server-cert-ed25519. Ed25519 has no x509v3-* SSH algorithm name, so
# this certificate exercises the rejection of an unmappable key type. Its key
# is not a host key, so it is dropped once the certificate is signed.
openssl genpkey -algorithm ed25519 -out server-cert-ed25519-key.pem
openssl req -subj '/C=US/ST=Washington/L=Seattle/O=Eliptic/OU=Ed25519/CN=www.wolfssl.com/emailAddress=server@example.com' -key server-cert-ed25519-key.pem -out server-cert-ed25519.csr -config "$CONFIG" -new -nodes
openssl x509 -req -in server-cert-ed25519.csr -days 3650 -extfile "$CONFIG" -extensions v3_server -CA ca-cert-ecc.pem -CAkey ca-key-ecc.pem -text -out server-cert-ed25519.pem -set_serial 9
openssl x509 -in server-cert-ed25519.pem -outform DER -out server-cert-ed25519.der
rm -f server-cert-ed25519-key.pem server-cert-ed25519.csr
rm index.*
if [ -n "$1" ]; then
rm -f "$CONFIG"

Binary file not shown.

View File

@ -0,0 +1,50 @@
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 9 (0x9)
Signature Algorithm: ecdsa-with-SHA256
Issuer: C=US, ST=Washington, L=Seattle, O=wolfSSL, OU=Development, CN=www.wolfssl.com, emailAddress=ca@example.com
Validity
Not Before: Aug 3 05:11:17 2026 GMT
Not After : Jul 31 05:11:17 2036 GMT
Subject: C=US, ST=Washington, L=Seattle, O=Eliptic, OU=Ed25519, CN=www.wolfssl.com, emailAddress=server@example.com
Subject Public Key Info:
Public Key Algorithm: ED25519
ED25519 Public-Key:
pub:
69:97:cf:00:fe:3c:27:d0:1b:3f:14:60:5a:b9:68:
a1:8c:89:64:93:ee:89:8c:48:31:a1:05:b1:f6:77:
bb:57
X509v3 extensions:
X509v3 Subject Key Identifier:
F7:D0:2B:CD:7D:A0:B7:1B:90:EB:F3:E8:E6:D0:A5:E9:C7:5F:E6:72
X509v3 Authority Key Identifier:
keyid:56:8E:9A:C3:F0:42:DE:18:B9:45:55:6E:F9:93:CF:EA:C3:F3:A5:21
DirName:/C=US/ST=Washington/L=Seattle/O=wolfSSL/OU=Development/CN=www.wolfssl.com/emailAddress=ca@example.com
serial:06
X509v3 Subject Alternative Name:
DNS:example, IP Address:127.0.0.1
Signature Algorithm: ecdsa-with-SHA256
Signature Value:
30:44:02:20:58:41:57:d7:27:fd:e0:a6:25:bf:40:11:31:5a:
e1:6d:7c:60:35:dc:04:f9:6e:2e:8c:d5:4b:41:1d:65:d8:16:
02:20:36:9c:e1:fa:4d:e1:aa:1f:19:4e:41:99:4d:79:ee:ed:
fb:7c:68:7e:0a:48:c6:12:7d:44:12:be:59:55:d3:6f
-----BEGIN CERTIFICATE-----
MIIC7jCCApWgAwIBAgIBCTAKBggqhkjOPQQDAjCBlTELMAkGA1UEBhMCVVMxEzAR
BgNVBAgMCldhc2hpbmd0b24xEDAOBgNVBAcMB1NlYXR0bGUxEDAOBgNVBAoMB3dv
bGZTU0wxFDASBgNVBAsMC0RldmVsb3BtZW50MRgwFgYDVQQDDA93d3cud29sZnNz
bC5jb20xHTAbBgkqhkiG9w0BCQEWDmNhQGV4YW1wbGUuY29tMB4XDTI2MDgwMzA1
MTExN1oXDTM2MDczMTA1MTExN1owgZUxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApX
YXNoaW5ndG9uMRAwDgYDVQQHDAdTZWF0dGxlMRAwDgYDVQQKDAdFbGlwdGljMRAw
DgYDVQQLDAdFZDI1NTE5MRgwFgYDVQQDDA93d3cud29sZnNzbC5jb20xITAfBgkq
hkiG9w0BCQEWEnNlcnZlckBleGFtcGxlLmNvbTAqMAUGAytlcAMhAGmXzwD+PCfQ
Gz8UYFq5aKGMiWST7omMSDGhBbH2d7tXo4IBATCB/jAdBgNVHQ4EFgQU99ArzX2g
txuQ6/Po5tCl6cdf5nIwgcIGA1UdIwSBujCBt4AUVo6aw/BC3hi5RVVu+ZPP6sPz
pSGhgZukgZgwgZUxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApXYXNoaW5ndG9uMRAw
DgYDVQQHDAdTZWF0dGxlMRAwDgYDVQQKDAd3b2xmU1NMMRQwEgYDVQQLDAtEZXZl
bG9wbWVudDEYMBYGA1UEAwwPd3d3LndvbGZzc2wuY29tMR0wGwYJKoZIhvcNAQkB
Fg5jYUBleGFtcGxlLmNvbYIBBjAYBgNVHREEETAPggdleGFtcGxlhwR/AAABMAoG
CCqGSM49BAMCA0cAMEQCIFhBV9cn/eCmJb9AETFa4W18YDXcBPluLozVS0EdZdgW
AiA2nOH6TeGqHxlOQZlNee7t+3xofgpIxhJ9RBK+WVXTbw==
-----END CERTIFICATE-----

View File

@ -1951,13 +1951,9 @@ int IdentifyAsn1Key(const byte* in, word32 inSz, int isPrivate, void* heap,
ret = wc_MlDsaKey_PublicKeyDecode(&key->ks.mldsa.key,
in, inSz, &idx);
if (ret != 0) {
/* Length-only fallback for local key/cert loading
* (wolfSSH_ReadKey_buffer_ex / IdentifyCert) when SPKI
* OID decode fails: wc_MlDsaKey_ImportPubRaw accepts an
* ML-DSA blob of exactly 1312/1952/2592 bytes and tags
* it ID_MLDSA44/65/87. Not used on the remote user-auth
* path; do not wire this probe into remote auth assuming
* it cryptographically validates the key. */
/* Local loading only: size alone tags a raw ML-DSA
* blob when SPKI OID decode fails. This does not
* validate the key, so keep it off remote auth. */
struct { byte level; byte id; } kProbe[3];
word32 nProbe = 0, li;
#ifndef WOLFSSH_NO_MLDSA44
@ -2076,17 +2072,10 @@ int IdentifyAsn1Key(const byte* in, word32 inSz, int isPrivate, void* heap,
#ifdef WOLFSSH_CERTS
/*
* Identifies the flavor of an X.509 certificate, RSA, ML-DSA or ECDSA, returns
* the key type ID. The process is to decode the certificate and pass the
* public key to IdentifyAsn1Key.
*
* @param in certificate to identify
* @param inSz size of certificate
* @param heap heap to use for memory allocation
* @return keyId as int, WS_MEMORY_E, WS_UNIMPLEMENTED_E
*/
static int IdentifyCert(const byte* in, word32 inSz, void* heap)
/* Identifies the key held inside an X.509 certificate, returning its plain
key type ID or a WS_ error. See IdentifyCert() for the x509v3-* algorithm
ID sent on the wire. */
static int IdentifyCertKey(const byte* in, word32 inSz, void* heap)
{
struct DecodedCert* cert = NULL;
#ifndef WOLFSSH_SMALL_STACK
@ -2106,13 +2095,22 @@ static int IdentifyCert(const byte* in, word32 inSz, void* heap)
}
#endif
/* Each wolfSSL result below is mapped where it is produced, so this
function returns only a key ID or a WS_ code. */
if (ret == 0) {
wc_InitDecodedCert(cert, in, inSz, heap);
ret = wc_ParseCert(cert, CERT_TYPE, 0, NULL);
if (ret != 0) {
ret = WS_PARSE_E;
}
}
if (ret == 0) {
/* Asking with no buffer answers with the length and LENGTH_ONLY_E. */
ret = wc_GetPubKeyDerFromCert(cert, NULL, &keySz);
if (ret == LENGTH_ONLY_E) {
if (ret != LENGTH_ONLY_E) {
ret = WS_PARSE_E;
}
else {
ret = 0;
key = (byte*)WMALLOC(keySz, heap, DYNTYPE_PUBKEY);
if (key == NULL) {
@ -2123,6 +2121,9 @@ static int IdentifyCert(const byte* in, word32 inSz, void* heap)
if (ret == 0) {
ret = wc_GetPubKeyDerFromCert(cert, key, &keySz);
if (ret != 0) {
ret = WS_PARSE_E;
}
}
if (ret == 0) {
@ -2233,6 +2234,31 @@ static INLINE byte CertTypeForId(byte id)
return id;
}
/* Identifies an X.509 certificate, returning the x509v3-* algorithm ID sent
on the wire. A key type with no x509v3 name in this build is rejected
rather than reported under its plain key name. */
int IdentifyCert(const byte* in, word32 inSz, void* heap)
{
byte certId;
int ret;
ret = IdentifyCertKey(in, inSz, heap);
if (ret >= 0) {
certId = CertTypeForId((byte)ret);
if (certId == (byte)ret) {
WLOG(WS_LOG_DEBUG, "No x509v3 algorithm for this certificate");
ret = WS_INVALID_ALGO_ID;
}
else {
ret = (int)certId;
}
}
return ret;
}
#define HINTISSET(x) ((x) != WOLFSSH_MAX_PVT_KEYS)
static int UpdateHostCertificates(WOLFSSH_CTX* ctx,
@ -2326,18 +2352,14 @@ static int UpdateHostCertificates(WOLFSSH_CTX* ctx,
}
static int SetHostCertificate(WOLFSSH_CTX* ctx,
byte keyId, byte* der, word32 derSz, int dynamicType)
byte certId, byte* der, word32 derSz, int dynamicType)
{
/*
* The keyId is for the key inside the certificate. wolfSSH_ProcessBuffer
* will decode the certificate, get the public key inside, and identify
* that. keyId will be: ssh-rsa, ecdsa-sha2-nistp256, etc.
*/
/* The certId is the x509v3-* algorithm the certificate is presented as,
* identified by wolfSSH_ProcessBuffer before calling here. */
word32 destIdx,
certIdx = WOLFSSH_MAX_PVT_KEYS, keyIdx = WOLFSSH_MAX_PVT_KEYS;
int ret = WS_SUCCESS;
byte certId = CertTypeForId(keyId);
/* Look for the specified certId. Add it if not present,
* replace it if present. Call UpdateHostCertificate().
@ -2347,7 +2369,10 @@ static int SetHostCertificate(WOLFSSH_CTX* ctx,
if (ctx->privateKey[destIdx].publicKeyFmt == certId) {
certIdx = destIdx;
}
if (ctx->privateKey[destIdx].publicKeyFmt == keyId) {
/* The key for this certificate sits in the slot whose plain
algorithm maps onto certId. */
else if (CertTypeForId(ctx->privateKey[destIdx].publicKeyFmt)
== certId) {
keyIdx = destIdx;
}
}
@ -2618,12 +2643,16 @@ int wolfSSH_ProcessBuffer(WOLFSSH_CTX* ctx,
WFREE(der, heap, dynamicType);
return ret;
}
keyId = (byte)ret;
ret = SetHostCertificate(ctx, keyId, der, derSz, dynamicType);
ret = SetHostCertificate(ctx, (byte)ret, der, derSz, dynamicType);
}
else if (type == BUFTYPE_CA) {
if (ctx->certMan != NULL) {
ret = wolfSSH_CERTMAN_LoadRootCA_buffer(ctx->certMan, der, derSz);
/* That call answers in wolfSSL codes apart from these two, so
anything else becomes one of ours. */
if (ret != WS_SUCCESS && ret != WS_BAD_ARGUMENT) {
ret = WS_PARSE_E;
}
}
else {
WLOG(WS_LOG_DEBUG, "Error no cert manager set");

429
src/ssh.c
View File

@ -1786,6 +1786,9 @@ char* wolfSSH_GetUsername(WOLFSSH* ssh)
#ifndef WOLFSSH_NO_MLDSA
#include <wolfssl/wolfcrypt/dilithium.h>
#endif
#ifdef WOLFSSH_OSSH_CERTS
#include <wolfssh/ossh.h>
#endif
union wolfSSH_key {
#ifndef WOLFSSH_NO_RSA
@ -2373,34 +2376,265 @@ int wolfSSH_ReadPublicKey_buffer(const byte* in, word32 inSz, int format,
}
#if defined(WOLFSSH_CERTS) || defined(WOLFSSH_OSSH_CERTS)
#ifdef WOLFSSH_CERTS
static const char* CertBeginPrefix = "-----BEGIN CERTIFICATE-----";
#endif
/* Longest algorithm name is ecdsa-sha2-nistp521-cert-v01@openssh.com. */
#define WOLFSSH_MAX_CERT_ALGO_NAME_SZ 48
/* Identifies a certificate from its content, without decoding or allocating.
An x509v3-* line holds a wire chain, not a certificate, so it is declined. */
static int SniffCertForm(const byte* in, word32 inSz, byte* flavor,
byte* certId)
{
#ifdef WOLFSSH_OSSH_CERTS
word32 tokenSz = 0;
byte id;
#endif
int ret = WS_BAD_FILETYPE_E;
#ifdef WOLFSSH_CERTS
if (in[0] == 0x30) {
*flavor = WOLFSSH_CERT_FLAVOR_X509;
ret = WOLFSSH_FORMAT_ASN1;
}
/* Searched for, not anchored: openssl writes a dump ahead of the header. */
else if (WSTRNSTR((const char*)in, CertBeginPrefix, inSz) != NULL) {
*flavor = WOLFSSH_CERT_FLAVOR_X509;
ret = WOLFSSH_FORMAT_PEM;
}
#endif /* WOLFSSH_CERTS */
#ifdef WOLFSSH_OSSH_CERTS
if (ret == WS_BAD_FILETYPE_E) {
while (tokenSz < inSz && tokenSz < WOLFSSH_MAX_CERT_ALGO_NAME_SZ
&& in[tokenSz] != ' ' && in[tokenSz] != '\t'
&& in[tokenSz] != '\r' && in[tokenSz] != '\n') {
tokenSz++;
}
if (tokenSz > 0 && tokenSz < WOLFSSH_MAX_CERT_ALGO_NAME_SZ) {
id = NameToId((const char*)in, tokenSz);
switch (id) {
case ID_OSSH_CERT_RSA:
case ID_OSSH_CERT_ECDSA_SHA2_NISTP256:
case ID_OSSH_CERT_ECDSA_SHA2_NISTP384:
case ID_OSSH_CERT_ECDSA_SHA2_NISTP521:
case ID_OSSH_CERT_ED25519:
*flavor = WOLFSSH_CERT_FLAVOR_OSSH;
*certId = id;
ret = WOLFSSH_FORMAT_SSH;
break;
default:
break;
}
}
}
#else
WOLFSSH_UNUSED(certId);
#endif /* WOLFSSH_OSSH_CERTS */
return ret;
}
#ifdef WOLFSSH_CERTS
/* Decodes a PEM certificate to DER and identifies it. */
static int DoPemCert(const byte* in, word32 inSz, byte** out, word32* outSz,
const byte** outType, word32* outTypeSz, void* heap)
{
byte* der;
word32 derSz = 0;
int ret;
der = (byte*)WMALLOC(inSz, heap, DYNTYPE_CERT);
if (der == NULL) {
return WS_MEMORY_E;
}
ret = wc_CertPemToDer(in, (int)inSz, der, (int)inSz, CERT_TYPE);
if (ret <= 0) {
WLOG(WS_LOG_DEBUG, "PEM to DER of certificate failed.");
WFREE(der, heap, DYNTYPE_CERT);
return WS_BAD_FILE_E;
}
derSz = (word32)ret;
ret = IdentifyCert(der, derSz, heap);
if (ret < 0) {
WFREE(der, heap, DYNTYPE_CERT);
}
else {
*out = der;
*outSz = derSz;
*outType = (const byte*)IdToName((byte)ret);
*outTypeSz = (word32)WSTRLEN((const char*)*outType);
ret = WS_SUCCESS;
}
return ret;
}
/* Identifies a DER certificate before copying it out. */
static int DoDerCert(const byte* in, word32 inSz, byte** out, word32* outSz,
const byte** outType, word32* outTypeSz, void* heap)
{
byte* der;
int ret;
ret = IdentifyCert(in, inSz, heap);
if (ret >= 0) {
der = (byte*)WMALLOC(inSz, heap, DYNTYPE_CERT);
if (der == NULL) {
ret = WS_MEMORY_E;
}
else {
WMEMCPY(der, in, inSz);
*out = der;
*outSz = inSz;
*outType = (const byte*)IdToName((byte)ret);
*outTypeSz = (word32)WSTRLEN((const char*)*outType);
ret = WS_SUCCESS;
}
}
return ret;
}
#endif /* WOLFSSH_CERTS */
#ifdef WOLFSSH_OSSH_CERTS
/* Decodes an OpenSSH certificate line and checks that it parses. */
static int DoOsshCert(const byte* in, word32 inSz, byte certId, byte** out,
word32* outSz, const byte** outType, word32* outTypeSz, void* heap)
{
WS_OsshCert cert;
byte* blob;
int ret;
blob = (byte*)WMALLOC(inSz, heap, DYNTYPE_CERT);
if (blob == NULL) {
return WS_MEMORY_E;
}
/* DoSshPubKey decodes into a non-NULL *out rather than allocating, so it
fills blob and leaves it ours to free. Were it to allocate instead,
blob would leak and the free below would take the wrong type. */
*out = blob;
*outSz = inSz;
ret = DoSshPubKey(in, inSz, out, outSz, outType, outTypeSz, heap);
if (ret == WS_SUCCESS) {
/* The blob has no envelope, so only a parse tells it from a key. */
ret = OsshCertParse(&cert, certId, *out, *outSz);
if (ret != WS_SUCCESS) {
WLOG(WS_LOG_DEBUG, "OpenSSH certificate is malformed.");
}
}
if (ret != WS_SUCCESS) {
WFREE(blob, heap, DYNTYPE_CERT);
*out = NULL;
*outSz = 0;
}
return ret;
}
#endif /* WOLFSSH_OSSH_CERTS */
/* Reads a certificate from the buffer in to out, detecting its form from the
content. See ssh.h for what the caller owns. */
int wolfSSH_ReadCert_buffer(const byte* in, word32 inSz,
byte** out, word32* outSz, const byte** outType, word32* outTypeSz,
byte* flavor, void* heap)
{
byte certId = ID_UNKNOWN;
int format;
int ret;
if (in == NULL || inSz == 0 || out == NULL || outSz == NULL ||
outType == NULL || outTypeSz == NULL || flavor == NULL) {
return WS_BAD_ARGUMENT;
}
*out = NULL;
*outSz = 0;
*outType = NULL;
*outTypeSz = 0;
*flavor = WOLFSSH_CERT_FLAVOR_UNKNOWN;
format = SniffCertForm(in, inSz, flavor, &certId);
if (format < 0) {
WLOG(WS_LOG_DEBUG, "Unable to identify the certificate");
return format;
}
ret = WS_BAD_FILETYPE_E;
switch (format) {
#ifdef WOLFSSH_CERTS
case WOLFSSH_FORMAT_PEM:
ret = DoPemCert(in, inSz, out, outSz, outType, outTypeSz, heap);
break;
case WOLFSSH_FORMAT_ASN1:
ret = DoDerCert(in, inSz, out, outSz, outType, outTypeSz, heap);
break;
#endif /* WOLFSSH_CERTS */
#ifdef WOLFSSH_OSSH_CERTS
case WOLFSSH_FORMAT_SSH:
ret = DoOsshCert(in, inSz, certId, out, outSz, outType, outTypeSz,
heap);
break;
#endif /* WOLFSSH_OSSH_CERTS */
default:
break;
}
/* The sniff names the form and a decoder may fill an out parameter before
failing, so a failure clears them all rather than leave a partial
answer. Decoders free whatever they allocated. */
if (ret != WS_SUCCESS) {
*out = NULL;
*outSz = 0;
*outType = NULL;
*outTypeSz = 0;
*flavor = WOLFSSH_CERT_FLAVOR_UNKNOWN;
}
return ret;
}
#endif /* WOLFSSH_CERTS || WOLFSSH_OSSH_CERTS */
#if !defined(NO_FILESYSTEM) && !defined(WOLFSSH_USER_FILESYSTEM)
/* Reads a key from the file name into a buffer. If the key starts with the
string "ssh-rsa" or "ecdsa-sha2-nistp256", it is considered an SSH format
public key, if it has "----BEGIN" it is considered PEM formatted,
otherwise it is considered an ASN.1 private key. The buffer is passed to
wolfSSH_ReadKey_buffer() for processing. */
int wolfSSH_ReadKey_file(const char* name,
byte** out, word32* outSz, const byte** outType, word32* outTypeSz,
byte* isPrivate, void* heap)
/* Reads the file name into a buffer allocated with an extra byte holding a
nul terminator. The caller frees out with DYNTYPE_FILE. */
static int ReadFileIntoBuffer(const char* name, byte** out, word32* outSz,
void* heap)
{
WFILE* file;
#ifdef MICROCHIP_MPLAB_HARMONY
WFILE f = WBADFILE;
#endif
byte* in;
word32 inSz;
int format;
int ret;
if (name == NULL)
return WS_BAD_FILE_E;
if (out == NULL || outSz == NULL || outType == NULL || outTypeSz == NULL ||
isPrivate == NULL)
return WS_BAD_ARGUMENT;
#ifdef MICROCHIP_MPLAB_HARMONY
file = &f;
ret = WFOPEN(NULL, &file, name, WOLFSSH_O_RDONLY);
@ -2429,10 +2663,43 @@ int wolfSSH_ReadKey_file(const char* name,
}
ret = (int)WFREAD(NULL, in, 1, inSz, file);
WFCLOSE(NULL, file);
if (ret <= 0 || (word32)ret != inSz) {
ret = WS_BAD_FILE_E;
WS_FORCEZERO(in, inSz);
WFREE(in, heap, DYNTYPE_FILE);
return WS_BAD_FILE_E;
}
else {
in[inSz] = 0;
*out = in;
*outSz = inSz;
return WS_SUCCESS;
}
/* Reads a key from the file name into a buffer. An SSH algorithm name marks
an SSH format public key and "-----BEGIN " a PEM one, otherwise it is an
ASN.1 private key. The buffer goes to wolfSSH_ReadKey_buffer_ex(). */
int wolfSSH_ReadKey_file(const char* name,
byte** out, word32* outSz, const byte** outType, word32* outTypeSz,
byte* isPrivate, void* heap)
{
byte* in;
word32 inSz;
int format;
int ret;
if (name == NULL)
return WS_BAD_FILE_E;
if (out == NULL || outSz == NULL || outType == NULL || outTypeSz == NULL ||
isPrivate == NULL)
return WS_BAD_ARGUMENT;
ret = ReadFileIntoBuffer(name, &in, &inSz, heap);
if (ret == WS_SUCCESS) {
if (WSTRNSTR((const char*)in, "ssh-rsa", inSz) == (const char*)in
|| WSTRNSTR((const char*)in,
"ecdsa-sha2-nistp", inSz) == (const char*)in
@ -2444,7 +2711,6 @@ int wolfSSH_ReadKey_file(const char* name,
"x509v3-ssh-mldsa-", inSz) == (const char*)in) {
*isPrivate = 0;
format = WOLFSSH_FORMAT_SSH;
in[inSz] = 0;
}
else if (WSTRNSTR((const char*)in, PrivBeginOpenSSH, inSz) != NULL) {
*isPrivate = 1;
@ -2464,15 +2730,55 @@ int wolfSSH_ReadKey_file(const char* name,
ret = wolfSSH_ReadKey_buffer_ex(in, inSz, format,
out, outSz, outType, outTypeSz, *isPrivate, heap);
}
WFCLOSE(NULL, file);
WS_FORCEZERO(in, inSz);
WFREE(in, heap, DYNTYPE_FILE);
WS_FORCEZERO(in, inSz);
WFREE(in, heap, DYNTYPE_FILE);
}
return ret;
}
#if defined(WOLFSSH_CERTS) || defined(WOLFSSH_OSSH_CERTS)
/* Reads a certificate file and passes it to wolfSSH_ReadCert_buffer(). */
int wolfSSH_ReadCert_file(const char* name,
byte** out, word32* outSz, const byte** outType, word32* outTypeSz,
byte* flavor, void* heap)
{
byte* in;
word32 inSz;
int ret;
if (name == NULL)
return WS_BAD_FILE_E;
if (out == NULL || outSz == NULL || outType == NULL || outTypeSz == NULL ||
flavor == NULL)
return WS_BAD_ARGUMENT;
*out = NULL;
*outSz = 0;
*outType = NULL;
*outTypeSz = 0;
*flavor = WOLFSSH_CERT_FLAVOR_UNKNOWN;
ret = ReadFileIntoBuffer(name, &in, &inSz, heap);
if (ret == WS_SUCCESS) {
ret = wolfSSH_ReadCert_buffer(in, inSz,
out, outSz, outType, outTypeSz, flavor, heap);
/* A certificate is public, but the file may hold a private key
beside it, so scrub it like the key reader does. */
WS_FORCEZERO(in, inSz);
WFREE(in, heap, DYNTYPE_FILE);
}
return ret;
}
#endif /* WOLFSSH_CERTS || WOLFSSH_OSSH_CERTS */
#endif
@ -2930,6 +3236,81 @@ int wolfSSH_CTX_AddRootCert_buffer(WOLFSSH_CTX* ctx,
return ret;
}
#if !defined(NO_FILESYSTEM) && !defined(WOLFSSH_USER_FILESYSTEM)
/* Reads a certificate file and hands the PEM or DER it holds to the CTX. */
static int UseCertFile(WOLFSSH_CTX* ctx, const char* name, int type)
{
byte* in;
word32 inSz;
byte certId = ID_UNKNOWN;
byte flavor = WOLFSSH_CERT_FLAVOR_UNKNOWN;
int format;
int ret;
if (ctx == NULL || name == NULL) {
return WS_BAD_ARGUMENT;
}
ret = ReadFileIntoBuffer(name, &in, &inSz, ctx->heap);
if (ret == WS_SUCCESS) {
format = SniffCertForm(in, inSz, &flavor, &certId);
if (format < 0) {
ret = format;
}
else if (format == WOLFSSH_FORMAT_SSH) {
/* A CTX takes the certificate itself, not a public key line. */
WLOG(WS_LOG_DEBUG, "Certificate file is not PEM or DER");
ret = WS_BAD_FILETYPE_E;
}
else {
ret = wolfSSH_ProcessBuffer(ctx, in, inSz, format, type);
}
WS_FORCEZERO(in, inSz);
WFREE(in, ctx->heap, DYNTYPE_FILE);
}
return ret;
}
/* Load in a X509 certificate file that has public key to use
* return WS_SUCCESS on success
*/
int wolfSSH_CTX_UseCert_file(WOLFSSH_CTX* ctx, const char* name)
{
int ret;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_CTX_UseCert_file()");
ret = UseCertFile(ctx, name, BUFTYPE_CERT);
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_CTX_UseCert_file(), ret = %d", ret);
return ret;
}
/* Add a CA file for verifying the peer's certificate with.
* returns WS_SUCCESS on success
*/
int wolfSSH_CTX_AddRootCert_file(WOLFSSH_CTX* ctx, const char* name)
{
int ret;
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_CTX_AddRootCert_file()");
ret = UseCertFile(ctx, name, BUFTYPE_CA);
WLOG(WS_LOG_DEBUG,
"Leaving wolfSSH_CTX_AddRootCert_file(), ret = %d", ret);
return ret;
}
#endif /* !NO_FILESYSTEM && !WOLFSSH_USER_FILESYSTEM */
#endif /* WOLFSSH_CERTS */

View File

@ -53,6 +53,12 @@
#endif
#ifdef WOLFSSH_OSSH_CERTS
#include <wolfssh/ossh.h>
#ifdef WOLFSSL_BASE64_ENCODE
/* Declared rather than including coding.h, whose Base16_Decode
* collides with the one wolfssh/test.h defines. */
WOLFSSL_API int Base64_Encode_NoNl(const byte* in, word32 inLen,
byte* out, word32* outLen);
#endif
#endif
#if defined(WOLFSSH_SFTP) || defined(WOLFSSH_SCP)
@ -686,8 +692,10 @@ static void test_wolfSSH_CTX_UseCert_buffer(void)
AssertIntEQ(WS_BAD_ARGUMENT,
wolfSSH_CTX_UseCert_buffer(ctx, NULL, 0, WOLFSSH_FORMAT_PEM));
#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP256
AssertIntEQ(WS_SUCCESS,
wolfSSH_CTX_UseCert_buffer(ctx, cert, certSz, WOLFSSH_FORMAT_PEM));
#endif
AssertIntEQ(WS_BAD_FILETYPE_E,
wolfSSH_CTX_UseCert_buffer(ctx, cert, certSz, WOLFSSH_FORMAT_ASN1));
@ -703,8 +711,10 @@ static void test_wolfSSH_CTX_UseCert_buffer(void)
AssertNotNull(cert);
AssertIntNE(0, certSz);
#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP256
AssertIntEQ(WS_SUCCESS,
wolfSSH_CTX_UseCert_buffer(ctx, cert, certSz, WOLFSSH_FORMAT_ASN1));
#endif
wolfSSH_CTX_free(ctx);
free(cert);
@ -712,6 +722,364 @@ static void test_wolfSSH_CTX_UseCert_buffer(void)
}
#if defined(WOLFSSH_CERTS) || defined(WOLFSSH_OSSH_CERTS)
/* Public key lines. An x509v3-* line carries an RFC 6187 wire chain rather
* than a certificate, so this API declines it the same as a plain key. */
static const char x509v3EccLine[] =
"x509v3-ecdsa-sha2-nistp256 AAAAB3NzaC1yc2EAAAA=\n";
static const char sshRsaLine[] = "ssh-rsa AAAAB3NzaC1yc2EAAAA=\n";
static const byte notACert[] = { 0x01, 0x02, 0x03, 0x04, 0x05 };
#if defined(WOLFSSH_OSSH_CERTS) && !defined(WOLFSSH_NO_ED25519) && \
!defined(NO_FILESYSTEM) && !defined(WOLFSSH_USER_FILESYSTEM)
#define WOLFSSH_TEST_OSSH_CERT_FILE
/* The name routes the line. The blob behind it is not a certificate, so a
* reader that decodes the blob rejects it there instead. */
static const char osshCertLine[] =
"ssh-ed25519-cert-v01@openssh.com AAAAB3NzaC1yc2EAAAA=\n";
static const char osshCertPath[] = "./ossh-cert-line.tmp";
/* Stages content in a file, the only form the file APIs take. Returns 0 on
* success. */
static int writeTmpFile(const char* path, const void* data, size_t sz)
{
FILE* f = NULL;
int ret = 0;
f = fopen(path, "wb");
if (f == NULL)
ret = -1;
if (ret == 0) {
if (fwrite(data, 1, sz, f) != sz)
ret = -2;
/* Close either way, but keep the write error as the reason. */
if (fclose(f) != 0 && ret == 0)
ret = -3;
}
return ret;
}
#endif /* OSSH_CERTS && ED25519 && FILESYSTEM */
static void test_wolfSSH_ReadCert_buffer(void)
{
byte* out = NULL;
word32 outSz = 0;
const byte* outType = NULL;
word32 outTypeSz = 0;
byte flavor = 0xFF;
#ifdef WOLFSSH_CERTS
byte* cert = NULL;
word32 certSz = 0;
#ifndef WOLFSSH_NO_ED25519
int ret;
#endif
#endif
/* Every out parameter is required, and so is a non-empty input. */
AssertIntEQ(WS_BAD_ARGUMENT, wolfSSH_ReadCert_buffer(NULL,
sizeof(notACert), &out, &outSz, &outType, &outTypeSz,
&flavor, NULL));
AssertIntEQ(WS_BAD_ARGUMENT, wolfSSH_ReadCert_buffer(notACert, 0,
&out, &outSz, &outType, &outTypeSz, &flavor, NULL));
AssertIntEQ(WS_BAD_ARGUMENT, wolfSSH_ReadCert_buffer(notACert,
sizeof(notACert), NULL, &outSz, &outType, &outTypeSz,
&flavor, NULL));
AssertIntEQ(WS_BAD_ARGUMENT, wolfSSH_ReadCert_buffer(notACert,
sizeof(notACert), &out, &outSz, &outType, &outTypeSz,
NULL, NULL));
/* Content that is not a certificate, a public key line included. */
AssertIntEQ(WS_BAD_FILETYPE_E, wolfSSH_ReadCert_buffer(notACert,
sizeof(notACert), &out, &outSz, &outType, &outTypeSz,
&flavor, NULL));
AssertIntEQ(WS_BAD_FILETYPE_E, wolfSSH_ReadCert_buffer(
(const byte*)sshRsaLine, (word32)WSTRLEN(sshRsaLine),
&out, &outSz, &outType, &outTypeSz, &flavor, NULL));
AssertIntEQ(WS_BAD_FILETYPE_E, wolfSSH_ReadCert_buffer(
(const byte*)x509v3EccLine, (word32)WSTRLEN(x509v3EccLine),
&out, &outSz, &outType, &outTypeSz, &flavor, NULL));
/* A rejection must not leave the caller's flavor standing as an answer. */
AssertIntEQ(flavor, WOLFSSH_CERT_FLAVOR_UNKNOWN);
#ifdef WOLFSSH_CERTS
AssertIntEQ(0, load_file("./keys/server-cert.pem", &cert, &certSz));
#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP256
AssertIntEQ(WS_SUCCESS, wolfSSH_ReadCert_buffer(cert, certSz,
&out, &outSz, &outType, &outTypeSz, &flavor, NULL));
AssertNotNull(out);
AssertIntGT(outSz, 0);
AssertIntEQ(out[0], 0x30);
AssertIntEQ(flavor, WOLFSSH_CERT_FLAVOR_X509);
AssertStrEQ((const char*)outType, "x509v3-ecdsa-sha2-nistp256");
AssertIntEQ(outTypeSz, (word32)WSTRLEN((const char*)outType));
WFREE(out, NULL, DYNTYPE_CERT);
out = NULL;
#else
/* Every fixture is ECDSA P-256. With its x509v3 name compiled out the key
* identifies but has no name to be reported under. */
AssertIntEQ(WS_INVALID_ALGO_ID, wolfSSH_ReadCert_buffer(cert, certSz,
&out, &outSz, &outType, &outTypeSz, &flavor, NULL));
AssertNull(out);
#endif
/* Half a PEM loses the CERTIFICATE header, so the sniff declines it. */
AssertIntEQ(WS_BAD_FILETYPE_E, wolfSSH_ReadCert_buffer(cert, certSz / 2,
&out, &outSz, &outType, &outTypeSz, &flavor, NULL));
free(cert);
cert = NULL;
AssertIntEQ(0, load_file("./keys/server-cert.der", &cert, &certSz));
#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP256
AssertIntEQ(WS_SUCCESS, wolfSSH_ReadCert_buffer(cert, certSz,
&out, &outSz, &outType, &outTypeSz, &flavor, NULL));
AssertNotNull(out);
AssertIntEQ(outSz, certSz);
AssertIntEQ(0, WMEMCMP(out, cert, certSz));
AssertIntEQ(flavor, WOLFSSH_CERT_FLAVOR_X509);
AssertStrEQ((const char*)outType, "x509v3-ecdsa-sha2-nistp256");
WFREE(out, NULL, DYNTYPE_CERT);
out = NULL;
#else
AssertIntEQ(WS_INVALID_ALGO_ID, wolfSSH_ReadCert_buffer(cert, certSz,
&out, &outSz, &outType, &outTypeSz, &flavor, NULL));
AssertNull(out);
#endif
free(cert);
cert = NULL;
/* Not a certificate behind the DER header. The wolfSSL error is mapped,
* so a WS_ code reaches the caller. The read above left a name behind, so
* this also shows a failure clearing one. */
AssertIntEQ(WS_PARSE_E, wolfSSH_ReadCert_buffer(
(const byte*)"\x30\x82\x01\x02",
4, &out, &outSz, &outType, &outTypeSz, &flavor, NULL));
AssertNull(out);
AssertIntEQ(outSz, 0);
AssertNull(outType);
AssertIntEQ(outTypeSz, 0);
/* A private key is the likeliest mistake, and the DER one also leads with
* 0x30, so only the parse tells them apart. */
AssertIntEQ(0, load_file("./keys/server-key-ecc.der", &cert, &certSz));
AssertIntEQ(WS_PARSE_E, wolfSSH_ReadCert_buffer(cert, certSz,
&out, &outSz, &outType, &outTypeSz, &flavor, NULL));
AssertNull(out);
free(cert);
cert = NULL;
/* The PEM one has no CERTIFICATE header, so the sniff declines it. Every
* out param goes in set to prove a rejection clears them all. */
AssertIntEQ(0, load_file("./keys/server-key-ecc.pem", &cert, &certSz));
out = cert;
outSz = 0xDEADBEEF;
outType = cert;
outTypeSz = 0xDEADBEEF;
flavor = WOLFSSH_CERT_FLAVOR_X509;
AssertIntEQ(WS_BAD_FILETYPE_E, wolfSSH_ReadCert_buffer(cert, certSz,
&out, &outSz, &outType, &outTypeSz, &flavor, NULL));
AssertNull(out);
AssertIntEQ(outSz, 0);
AssertNull(outType);
AssertIntEQ(outTypeSz, 0);
AssertIntEQ(flavor, WOLFSSH_CERT_FLAVOR_UNKNOWN);
free(cert);
cert = NULL;
#ifndef WOLFSSH_NO_ED25519
/* Ed25519 must never load: it has no x509v3 name, and today the key
* inside is not identified either. Which of the two rejections lands
* depends on how wolfSSL returns a cert's public key, so accept both. */
AssertIntEQ(0, load_file("./keys/server-cert-ed25519.der",
&cert, &certSz));
ret = wolfSSH_ReadCert_buffer(cert, certSz,
&out, &outSz, &outType, &outTypeSz, &flavor, NULL);
AssertTrue(ret == WS_UNIMPLEMENTED_E || ret == WS_INVALID_ALGO_ID);
AssertNull(out);
free(cert);
cert = NULL;
/* The PEM form makes that same DER first, so rejecting it must free it. */
AssertIntEQ(0, load_file("./keys/server-cert-ed25519.pem",
&cert, &certSz));
ret = wolfSSH_ReadCert_buffer(cert, certSz,
&out, &outSz, &outType, &outTypeSz, &flavor, NULL);
AssertTrue(ret == WS_UNIMPLEMENTED_E || ret == WS_INVALID_ALGO_ID);
AssertNull(out);
AssertIntEQ(flavor, WOLFSSH_CERT_FLAVOR_UNKNOWN);
free(cert);
cert = NULL;
#endif /* WOLFSSH_NO_ED25519 */
#endif /* WOLFSSH_CERTS */
}
static void test_wolfSSH_ReadCert_file(void)
{
/* The arguments are checked ahead of any certificate, so those cases hold for
* an OpenSSH-only build too. */
#if !defined(NO_FILESYSTEM) && !defined(WOLFSSH_USER_FILESYSTEM)
byte* out = NULL;
word32 outSz = 0;
const byte* outType = NULL;
word32 outTypeSz = 0;
byte flavor = 0xFF;
byte stale[1];
AssertIntEQ(WS_BAD_FILE_E, wolfSSH_ReadCert_file(NULL,
&out, &outSz, &outType, &outTypeSz, &flavor, NULL));
AssertIntEQ(WS_BAD_ARGUMENT, wolfSSH_ReadCert_file("./keys/server-cert.pem",
NULL, &outSz, &outType, &outTypeSz, &flavor, NULL));
/* A file that never opens still clears every out parameter. Sentinels go
in, so a stale pointer surviving the call would fail here. */
out = stale;
outSz = 0xDEADBEEF;
outType = stale;
outTypeSz = 0xDEADBEEF;
flavor = 0xFF;
AssertIntEQ(WS_BAD_FILE_E, wolfSSH_ReadCert_file("./keys/no-such-cert.pem",
&out, &outSz, &outType, &outTypeSz, &flavor, NULL));
AssertNull(out);
AssertIntEQ(outSz, 0);
AssertNull(outType);
AssertIntEQ(outTypeSz, 0);
AssertIntEQ(flavor, WOLFSSH_CERT_FLAVOR_UNKNOWN);
/* A directory fails further in, opening but not reading. */
out = stale;
outSz = 0xDEADBEEF;
flavor = 0xFF;
AssertIntEQ(WS_BAD_FILE_E, wolfSSH_ReadCert_file("./keys",
&out, &outSz, &outType, &outTypeSz, &flavor, NULL));
AssertNull(out);
AssertIntEQ(outSz, 0);
AssertIntEQ(flavor, WOLFSSH_CERT_FLAVOR_UNKNOWN);
#if defined(WOLFSSH_CERTS) && !defined(WOLFSSH_NO_ECDSA_SHA2_NISTP256)
AssertIntEQ(WS_SUCCESS, wolfSSH_ReadCert_file("./keys/server-cert.pem",
&out, &outSz, &outType, &outTypeSz, &flavor, NULL));
AssertNotNull(out);
AssertIntEQ(out[0], 0x30);
AssertIntEQ(flavor, WOLFSSH_CERT_FLAVOR_X509);
AssertStrEQ((const char*)outType, "x509v3-ecdsa-sha2-nistp256");
WFREE(out, NULL, DYNTYPE_CERT);
out = NULL;
AssertIntEQ(WS_SUCCESS, wolfSSH_ReadCert_file("./keys/server-cert.der",
&out, &outSz, &outType, &outTypeSz, &flavor, NULL));
AssertNotNull(out);
AssertIntEQ(out[0], 0x30);
AssertIntEQ(flavor, WOLFSSH_CERT_FLAVOR_X509);
WFREE(out, NULL, DYNTYPE_CERT);
#endif /* WOLFSSH_CERTS && !WOLFSSH_NO_ECDSA_SHA2_NISTP256 */
#endif
}
static void test_wolfSSH_CTX_UseCert_file(void)
{
#if defined(WOLFSSH_CERTS) && !defined(NO_FILESYSTEM) && \
!defined(WOLFSSH_USER_FILESYSTEM) && !defined(WOLFSSH_NO_SERVER)
WOLFSSH_CTX* ctx = NULL;
#ifndef WOLFSSH_NO_ED25519
int ret;
#endif
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
AssertNotNull(ctx);
AssertIntEQ(WS_BAD_ARGUMENT,
wolfSSH_CTX_UseCert_file(NULL, "./keys/server-cert.pem"));
AssertIntEQ(WS_BAD_ARGUMENT, wolfSSH_CTX_UseCert_file(ctx, NULL));
AssertIntEQ(WS_BAD_FILE_E,
wolfSSH_CTX_UseCert_file(ctx, "./keys/no-such-cert.pem"));
/* Both encodings load through the same call. */
#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP256
AssertIntEQ(WS_SUCCESS,
wolfSSH_CTX_UseCert_file(ctx, "./keys/server-cert.pem"));
AssertIntEQ(WS_SUCCESS,
wolfSSH_CTX_UseCert_file(ctx, "./keys/server-cert.der"));
#endif
/* A CTX takes the certificate itself, not a public key line. */
AssertIntEQ(WS_BAD_FILETYPE_E,
wolfSSH_CTX_UseCert_file(ctx, "./keys/id_ecdsa.pub"));
#ifdef WOLFSSH_TEST_OSSH_CERT_FILE
AssertIntEQ(0, writeTmpFile(osshCertPath, osshCertLine,
WSTRLEN(osshCertLine)));
AssertIntEQ(WS_BAD_FILETYPE_E, wolfSSH_CTX_UseCert_file(ctx, osshCertPath));
AssertIntEQ(0, remove(osshCertPath));
#endif
#ifndef WOLFSSH_NO_ED25519
/* Refused here too, on the same codes as wolfSSH_ReadCert_file(). */
ret = wolfSSH_CTX_UseCert_file(ctx, "./keys/server-cert-ed25519.der");
AssertTrue(ret == WS_UNIMPLEMENTED_E || ret == WS_INVALID_ALGO_ID);
ret = wolfSSH_CTX_UseCert_file(ctx, "./keys/server-cert-ed25519.pem");
AssertTrue(ret == WS_UNIMPLEMENTED_E || ret == WS_INVALID_ALGO_ID);
#endif
wolfSSH_CTX_free(ctx);
#endif
}
static void test_wolfSSH_CTX_AddRootCert_file(void)
{
#if defined(WOLFSSH_CERTS) && !defined(NO_FILESYSTEM) && \
!defined(WOLFSSH_USER_FILESYSTEM) && !defined(WOLFSSH_NO_SERVER)
WOLFSSH_CTX* ctx = NULL;
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
AssertNotNull(ctx);
AssertIntEQ(WS_BAD_ARGUMENT,
wolfSSH_CTX_AddRootCert_file(NULL, "./keys/ca-cert-ecc.pem"));
AssertIntEQ(WS_BAD_ARGUMENT, wolfSSH_CTX_AddRootCert_file(ctx, NULL));
AssertIntEQ(WS_BAD_FILE_E,
wolfSSH_CTX_AddRootCert_file(ctx, "./keys/no-such-ca.pem"));
AssertIntEQ(WS_SUCCESS,
wolfSSH_CTX_AddRootCert_file(ctx, "./keys/ca-cert-ecc.pem"));
AssertIntEQ(WS_SUCCESS,
wolfSSH_CTX_AddRootCert_file(ctx, "./keys/ca-cert-ecc.der"));
/* A CTX takes the certificate itself, not a public key line. */
AssertIntEQ(WS_BAD_FILETYPE_E,
wolfSSH_CTX_AddRootCert_file(ctx, "./keys/id_ecdsa.pub"));
/* The cert manager rejects a non-CA in wolfSSL's codes; this path maps. */
AssertIntEQ(WS_PARSE_E,
wolfSSH_CTX_AddRootCert_file(ctx, "./keys/server-key-ecc.der"));
#ifdef WOLFSSH_TEST_OSSH_CERT_FILE
AssertIntEQ(0, writeTmpFile(osshCertPath, osshCertLine,
WSTRLEN(osshCertLine)));
AssertIntEQ(WS_BAD_FILETYPE_E,
wolfSSH_CTX_AddRootCert_file(ctx, osshCertPath));
AssertIntEQ(0, remove(osshCertPath));
#endif
wolfSSH_CTX_free(ctx);
#endif
}
#else
static void test_wolfSSH_ReadCert_buffer(void) { ; }
static void test_wolfSSH_ReadCert_file(void) { ; }
static void test_wolfSSH_CTX_UseCert_file(void) { ; }
static void test_wolfSSH_CTX_AddRootCert_file(void) { ; }
#endif /* WOLFSSH_CERTS || WOLFSSH_OSSH_CERTS */
static void test_wolfSSH_CTX_UsePrivateKey_buffer_pem(void)
{
#if defined(WOLFSSH_CERTS) && !defined(WOLFSSH_NO_SERVER)
@ -2204,6 +2572,124 @@ static const byte ossh_vec_ecc[] = {
};
#endif /* WOLFSSH_NO_ECDSA_SHA2_NISTP256 */
#ifdef WOLFSSL_BASE64_ENCODE
/* Renders a blob as the "name base64" line an authorized_keys style file
* holds. Returns the length written to line. */
static word32 buildOsshCertLine(byte* line, word32 lineCap,
const byte* vec, word32 vecSz)
{
static const char name[] = "ssh-ed25519-cert-v01@openssh.com ";
word32 nameSz, b64Sz;
nameSz = (word32)WSTRLEN(name);
AssertIntGT(lineCap, nameSz + 2);
WMEMCPY(line, name, nameSz);
b64Sz = lineCap - nameSz - 2;
AssertIntEQ(0, Base64_Encode_NoNl(vec, vecSz, line + nameSz, &b64Sz));
line[nameSz + b64Sz] = '\n';
return nameSz + b64Sz + 1;
}
/* Reads a certificate vector back through the public API in the "name base64"
* line form an authorized_keys style file holds. */
static void test_wolfSSH_ReadCert_buffer_ossh(void)
{
byte line[2048];
byte* out = NULL;
const byte* outType = NULL;
word32 outSz = 0, outTypeSz = 0, lineSz;
byte flavor = 0xFF;
lineSz = buildOsshCertLine(line, (word32)sizeof(line),
ossh_vec_ed, (word32)sizeof(ossh_vec_ed));
AssertIntEQ(WS_SUCCESS, wolfSSH_ReadCert_buffer(line, lineSz,
&out, &outSz, &outType, &outTypeSz, &flavor, NULL));
AssertNotNull(out);
AssertIntEQ(outSz, (word32)sizeof(ossh_vec_ed));
AssertIntEQ(0, WMEMCMP(out, ossh_vec_ed, sizeof(ossh_vec_ed)));
AssertIntEQ(flavor, WOLFSSH_CERT_FLAVOR_OSSH);
AssertStrEQ((const char*)outType, "ssh-ed25519-cert-v01@openssh.com");
AssertIntEQ(outTypeSz, (word32)WSTRLEN((const char*)outType));
WFREE(out, NULL, DYNTYPE_CERT);
out = NULL;
/* Half a blob still forms a valid line, so the parse must reject it. The
* line names its algorithm, so outType is set before that parse runs and
* has to be taken back with the rest. */
lineSz = buildOsshCertLine(line, (word32)sizeof(line),
ossh_vec_ed, (word32)sizeof(ossh_vec_ed) / 2);
AssertIntLT(wolfSSH_ReadCert_buffer(line, lineSz,
&out, &outSz, &outType, &outTypeSz, &flavor, NULL), 0);
AssertNull(out);
AssertIntEQ(outSz, 0);
AssertNull(outType);
AssertIntEQ(outTypeSz, 0);
AssertIntEQ(flavor, WOLFSSH_CERT_FLAVOR_UNKNOWN);
}
#else
static void test_wolfSSH_ReadCert_buffer_ossh(void) { ; }
#endif /* WOLFSSL_BASE64_ENCODE */
#ifdef WOLFSSH_TEST_OSSH_CERT_FILE
/* Reads the same line form from a file. This is the only certificate the
* file reader takes in a build without X.509 support. */
static void test_wolfSSH_ReadCert_file_ossh(void)
{
byte* out = NULL;
const byte* outType = NULL;
word32 outSz = 0, outTypeSz = 0;
byte flavor = 0xFF;
#ifdef WOLFSSL_BASE64_ENCODE
byte line[2048];
word32 lineSz;
lineSz = buildOsshCertLine(line, (word32)sizeof(line),
ossh_vec_ed, (word32)sizeof(ossh_vec_ed));
AssertIntEQ(0, writeTmpFile(osshCertPath, line, lineSz));
AssertIntEQ(WS_SUCCESS, wolfSSH_ReadCert_file(osshCertPath,
&out, &outSz, &outType, &outTypeSz, &flavor, NULL));
AssertNotNull(out);
AssertIntEQ(outSz, (word32)sizeof(ossh_vec_ed));
AssertIntEQ(0, WMEMCMP(out, ossh_vec_ed, sizeof(ossh_vec_ed)));
AssertIntEQ(flavor, WOLFSSH_CERT_FLAVOR_OSSH);
AssertStrEQ((const char*)outType, "ssh-ed25519-cert-v01@openssh.com");
AssertIntEQ(outTypeSz, (word32)WSTRLEN((const char*)outType));
WFREE(out, NULL, DYNTYPE_CERT);
out = NULL;
AssertIntEQ(0, remove(osshCertPath));
#endif /* WOLFSSL_BASE64_ENCODE */
/* The name only routes the file; the blob still has to parse. Rejecting
* it clears the name the line had already supplied. */
AssertIntEQ(0, writeTmpFile(osshCertPath, osshCertLine,
WSTRLEN(osshCertLine)));
AssertIntLT(wolfSSH_ReadCert_file(osshCertPath,
&out, &outSz, &outType, &outTypeSz, &flavor, NULL), 0);
AssertNull(out);
AssertIntEQ(outSz, 0);
AssertNull(outType);
AssertIntEQ(outTypeSz, 0);
AssertIntEQ(flavor, WOLFSSH_CERT_FLAVOR_UNKNOWN);
AssertIntEQ(0, remove(osshCertPath));
}
#else
static void test_wolfSSH_ReadCert_file_ossh(void) { ; }
#endif /* WOLFSSH_TEST_OSSH_CERT_FILE */
/* Parse, verify the CA signature, and validate the options of each committed
* certificate vector; then flip the final signature byte and confirm the
* verification fails while the parse still succeeds. */
@ -5773,6 +6259,10 @@ int wolfSSH_ApiTest(int argc, char** argv)
test_wolfSSH_ConvertConsole();
test_wolfSSH_CTX_UsePrivateKey_buffer();
test_wolfSSH_CTX_UseCert_buffer();
test_wolfSSH_CTX_UseCert_file();
test_wolfSSH_CTX_AddRootCert_file();
test_wolfSSH_ReadCert_buffer();
test_wolfSSH_ReadCert_file();
test_wolfSSH_CTX_UsePrivateKey_buffer_pem();
test_wolfSSH_CTX_SetWindowPacketSize();
test_wolfSSH_CertMan();
@ -5801,6 +6291,8 @@ int wolfSSH_ApiTest(int argc, char** argv)
#endif
#ifdef WOLFSSH_OSSH_CERTS
#ifndef WOLFSSH_NO_ED25519
test_wolfSSH_ReadCert_buffer_ossh();
test_wolfSSH_ReadCert_file_ossh();
test_wolfSSH_OsshCert_valid();
#ifdef WOLFSSH_TEST_OSSH_VEC_ECC
test_wolfSSH_OsshCert_ecc_curve_mismatch();

View File

@ -1328,6 +1328,9 @@ WOLFSSH_LOCAL int GetOpenSshKey(WS_KeySignature *key,
WOLFSSH_LOCAL int GetOpenSshPublicKey(WS_KeySignature *key,
const byte* buf, word32 len, word32* idx);
#endif
#ifdef WOLFSSH_CERTS
WOLFSSH_LOCAL int IdentifyCert(const byte* in, word32 inSz, void* heap);
#endif
/* Parsing functions */

View File

@ -104,6 +104,19 @@ WOLFSSH_API int wolfSSH_ReadKey_file(const char* name,
byte** out, word32* outSz, const byte** outType, word32* outTypeSz,
byte* isPrivate, void* heap);
#if defined(WOLFSSH_CERTS) || defined(WOLFSSH_OSSH_CERTS)
/* Decodes a PEM/DER X.509 cert or OpenSSH cert line, detected from content.
* Caller frees out via heap; on failure every out param is cleared. */
WOLFSSH_API int wolfSSH_ReadCert_buffer(const byte* in, word32 inSz,
byte** out, word32* outSz, const byte** outType, word32* outTypeSz,
byte* flavor, void* heap);
#if !defined(NO_FILESYSTEM) && !defined(WOLFSSH_USER_FILESYSTEM)
WOLFSSH_API int wolfSSH_ReadCert_file(const char* name,
byte** out, word32* outSz, const byte** outType, word32* outTypeSz,
byte* flavor, void* heap);
#endif
#endif /* WOLFSSH_CERTS || WOLFSSH_OSSH_CERTS */
/* SetAlgoList* validate the list, returning WS_SUCCESS, WS_INVALID_ALGO_ID for
* a bad list, or WS_SSH_CTX_NULL_E / WS_SSH_NULL_E for a NULL ctx / ssh.
* Kex/Cipher/Mac reject NULL. Key accepts NULL only on a server, restoring the
@ -494,6 +507,13 @@ WOLFSSH_API int wolfSSH_CTX_UsePrivateKey_buffer(WOLFSSH_CTX* ctx,
const byte* cert, word32 certSz, int format);
WOLFSSH_API int wolfSSH_CTX_AddRootCert_buffer(WOLFSSH_CTX* ctx,
const byte* cert, word32 certSz, int format);
#if !defined(NO_FILESYSTEM) && !defined(WOLFSSH_USER_FILESYSTEM)
/* PEM or DER is detected from the file's content. */
WOLFSSH_API int wolfSSH_CTX_UseCert_file(WOLFSSH_CTX* ctx,
const char* name);
WOLFSSH_API int wolfSSH_CTX_AddRootCert_file(WOLFSSH_CTX* ctx,
const char* name);
#endif
#endif /* WOLFSSH_CERTS */
WOLFSSH_API int wolfSSH_CTX_SetWindowPacketSize(WOLFSSH_CTX* ctx,
word32 windowSz, word32 maxPacketSz);
@ -600,6 +620,16 @@ enum WS_FormatTypes {
};
/* X.509 certificates are consumed by UseCert/AddRootCert, OpenSSH ones by
the trusted user CA machinery. UNKNOWN leads so that a zeroed flavor is
not a claim about the content. */
enum WS_CertFlavors {
WOLFSSH_CERT_FLAVOR_UNKNOWN,
WOLFSSH_CERT_FLAVOR_X509,
WOLFSSH_CERT_FLAVOR_OSSH
};
/* bit map */
#define WOLFSSH_USERAUTH_PASSWORD 0x01
#define WOLFSSH_USERAUTH_PUBLICKEY 0x02