mirror of https://github.com/wolfSSL/wolfssh.git
Add certificate loading APIs with content-based format detection
parent
492e8cd65d
commit
c2d169872e
|
|
@ -83,6 +83,9 @@ keys/*-ossh-*cert.pub
|
||||||
random-test.txt
|
random-test.txt
|
||||||
random-test-result.txt
|
random-test-result.txt
|
||||||
test.dat
|
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
|
# test output
|
||||||
tests/*.test
|
tests/*.test
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ EXTRA_DIST+= \
|
||||||
keys/pubkeys-rsa.txt keys/passwd.txt keys/ca-cert-ecc.der \
|
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/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.der keys/server-cert.pem \
|
||||||
|
keys/server-cert-ed25519.der keys/server-cert-ed25519.pem \
|
||||||
keys/fred-cert.der keys/fred-cert.pem \
|
keys/fred-cert.der keys/fred-cert.pem \
|
||||||
keys/server-key.pem keys/fred-key.der keys/fred-key.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 \
|
keys/id_ecdsa keys/id_ecdsa.pub keys/id_rsa keys/id_rsa.pub \
|
||||||
|
|
|
||||||
|
|
@ -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 -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
|
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.*
|
rm index.*
|
||||||
if [ -n "$1" ]; then
|
if [ -n "$1" ]; then
|
||||||
rm -f "$CONFIG"
|
rm -f "$CONFIG"
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -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-----
|
||||||
|
|
@ -1951,13 +1951,9 @@ int IdentifyAsn1Key(const byte* in, word32 inSz, int isPrivate, void* heap,
|
||||||
ret = wc_MlDsaKey_PublicKeyDecode(&key->ks.mldsa.key,
|
ret = wc_MlDsaKey_PublicKeyDecode(&key->ks.mldsa.key,
|
||||||
in, inSz, &idx);
|
in, inSz, &idx);
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
/* Length-only fallback for local key/cert loading
|
/* Local loading only: size alone tags a raw ML-DSA
|
||||||
* (wolfSSH_ReadKey_buffer_ex / IdentifyCert) when SPKI
|
* blob when SPKI OID decode fails. This does not
|
||||||
* OID decode fails: wc_MlDsaKey_ImportPubRaw accepts an
|
* validate the key, so keep it off remote auth. */
|
||||||
* 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. */
|
|
||||||
struct { byte level; byte id; } kProbe[3];
|
struct { byte level; byte id; } kProbe[3];
|
||||||
word32 nProbe = 0, li;
|
word32 nProbe = 0, li;
|
||||||
#ifndef WOLFSSH_NO_MLDSA44
|
#ifndef WOLFSSH_NO_MLDSA44
|
||||||
|
|
@ -2076,17 +2072,10 @@ int IdentifyAsn1Key(const byte* in, word32 inSz, int isPrivate, void* heap,
|
||||||
|
|
||||||
|
|
||||||
#ifdef WOLFSSH_CERTS
|
#ifdef WOLFSSH_CERTS
|
||||||
/*
|
/* Identifies the key held inside an X.509 certificate, returning its plain
|
||||||
* Identifies the flavor of an X.509 certificate, RSA, ML-DSA or ECDSA, returns
|
key type ID or a WS_ error. See IdentifyCert() for the x509v3-* algorithm
|
||||||
* the key type ID. The process is to decode the certificate and pass the
|
ID sent on the wire. */
|
||||||
* public key to IdentifyAsn1Key.
|
static int IdentifyCertKey(const byte* in, word32 inSz, void* heap)
|
||||||
*
|
|
||||||
* @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)
|
|
||||||
{
|
{
|
||||||
struct DecodedCert* cert = NULL;
|
struct DecodedCert* cert = NULL;
|
||||||
#ifndef WOLFSSH_SMALL_STACK
|
#ifndef WOLFSSH_SMALL_STACK
|
||||||
|
|
@ -2106,13 +2095,22 @@ static int IdentifyCert(const byte* in, word32 inSz, void* heap)
|
||||||
}
|
}
|
||||||
#endif
|
#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) {
|
if (ret == 0) {
|
||||||
wc_InitDecodedCert(cert, in, inSz, heap);
|
wc_InitDecodedCert(cert, in, inSz, heap);
|
||||||
ret = wc_ParseCert(cert, CERT_TYPE, 0, NULL);
|
ret = wc_ParseCert(cert, CERT_TYPE, 0, NULL);
|
||||||
|
if (ret != 0) {
|
||||||
|
ret = WS_PARSE_E;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (ret == 0) {
|
if (ret == 0) {
|
||||||
|
/* Asking with no buffer answers with the length and LENGTH_ONLY_E. */
|
||||||
ret = wc_GetPubKeyDerFromCert(cert, NULL, &keySz);
|
ret = wc_GetPubKeyDerFromCert(cert, NULL, &keySz);
|
||||||
if (ret == LENGTH_ONLY_E) {
|
if (ret != LENGTH_ONLY_E) {
|
||||||
|
ret = WS_PARSE_E;
|
||||||
|
}
|
||||||
|
else {
|
||||||
ret = 0;
|
ret = 0;
|
||||||
key = (byte*)WMALLOC(keySz, heap, DYNTYPE_PUBKEY);
|
key = (byte*)WMALLOC(keySz, heap, DYNTYPE_PUBKEY);
|
||||||
if (key == NULL) {
|
if (key == NULL) {
|
||||||
|
|
@ -2123,6 +2121,9 @@ static int IdentifyCert(const byte* in, word32 inSz, void* heap)
|
||||||
|
|
||||||
if (ret == 0) {
|
if (ret == 0) {
|
||||||
ret = wc_GetPubKeyDerFromCert(cert, key, &keySz);
|
ret = wc_GetPubKeyDerFromCert(cert, key, &keySz);
|
||||||
|
if (ret != 0) {
|
||||||
|
ret = WS_PARSE_E;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ret == 0) {
|
if (ret == 0) {
|
||||||
|
|
@ -2233,6 +2234,31 @@ static INLINE byte CertTypeForId(byte id)
|
||||||
return 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)
|
#define HINTISSET(x) ((x) != WOLFSSH_MAX_PVT_KEYS)
|
||||||
|
|
||||||
static int UpdateHostCertificates(WOLFSSH_CTX* ctx,
|
static int UpdateHostCertificates(WOLFSSH_CTX* ctx,
|
||||||
|
|
@ -2326,18 +2352,14 @@ static int UpdateHostCertificates(WOLFSSH_CTX* ctx,
|
||||||
}
|
}
|
||||||
|
|
||||||
static int SetHostCertificate(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 certId is the x509v3-* algorithm the certificate is presented as,
|
||||||
* The keyId is for the key inside the certificate. wolfSSH_ProcessBuffer
|
* identified by wolfSSH_ProcessBuffer before calling here. */
|
||||||
* will decode the certificate, get the public key inside, and identify
|
|
||||||
* that. keyId will be: ssh-rsa, ecdsa-sha2-nistp256, etc.
|
|
||||||
*/
|
|
||||||
|
|
||||||
word32 destIdx,
|
word32 destIdx,
|
||||||
certIdx = WOLFSSH_MAX_PVT_KEYS, keyIdx = WOLFSSH_MAX_PVT_KEYS;
|
certIdx = WOLFSSH_MAX_PVT_KEYS, keyIdx = WOLFSSH_MAX_PVT_KEYS;
|
||||||
int ret = WS_SUCCESS;
|
int ret = WS_SUCCESS;
|
||||||
byte certId = CertTypeForId(keyId);
|
|
||||||
|
|
||||||
/* Look for the specified certId. Add it if not present,
|
/* Look for the specified certId. Add it if not present,
|
||||||
* replace it if present. Call UpdateHostCertificate().
|
* replace it if present. Call UpdateHostCertificate().
|
||||||
|
|
@ -2347,7 +2369,10 @@ static int SetHostCertificate(WOLFSSH_CTX* ctx,
|
||||||
if (ctx->privateKey[destIdx].publicKeyFmt == certId) {
|
if (ctx->privateKey[destIdx].publicKeyFmt == certId) {
|
||||||
certIdx = destIdx;
|
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;
|
keyIdx = destIdx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2618,12 +2643,16 @@ int wolfSSH_ProcessBuffer(WOLFSSH_CTX* ctx,
|
||||||
WFREE(der, heap, dynamicType);
|
WFREE(der, heap, dynamicType);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
keyId = (byte)ret;
|
ret = SetHostCertificate(ctx, (byte)ret, der, derSz, dynamicType);
|
||||||
ret = SetHostCertificate(ctx, keyId, der, derSz, dynamicType);
|
|
||||||
}
|
}
|
||||||
else if (type == BUFTYPE_CA) {
|
else if (type == BUFTYPE_CA) {
|
||||||
if (ctx->certMan != NULL) {
|
if (ctx->certMan != NULL) {
|
||||||
ret = wolfSSH_CERTMAN_LoadRootCA_buffer(ctx->certMan, der, derSz);
|
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 {
|
else {
|
||||||
WLOG(WS_LOG_DEBUG, "Error no cert manager set");
|
WLOG(WS_LOG_DEBUG, "Error no cert manager set");
|
||||||
|
|
|
||||||
429
src/ssh.c
429
src/ssh.c
|
|
@ -1786,6 +1786,9 @@ char* wolfSSH_GetUsername(WOLFSSH* ssh)
|
||||||
#ifndef WOLFSSH_NO_MLDSA
|
#ifndef WOLFSSH_NO_MLDSA
|
||||||
#include <wolfssl/wolfcrypt/dilithium.h>
|
#include <wolfssl/wolfcrypt/dilithium.h>
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef WOLFSSH_OSSH_CERTS
|
||||||
|
#include <wolfssh/ossh.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
union wolfSSH_key {
|
union wolfSSH_key {
|
||||||
#ifndef WOLFSSH_NO_RSA
|
#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)
|
#if !defined(NO_FILESYSTEM) && !defined(WOLFSSH_USER_FILESYSTEM)
|
||||||
|
|
||||||
/* Reads a key from the file name into a buffer. If the key starts with the
|
/* Reads the file name into a buffer allocated with an extra byte holding a
|
||||||
string "ssh-rsa" or "ecdsa-sha2-nistp256", it is considered an SSH format
|
nul terminator. The caller frees out with DYNTYPE_FILE. */
|
||||||
public key, if it has "----BEGIN" it is considered PEM formatted,
|
static int ReadFileIntoBuffer(const char* name, byte** out, word32* outSz,
|
||||||
otherwise it is considered an ASN.1 private key. The buffer is passed to
|
void* heap)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
|
|
||||||
WFILE* file;
|
WFILE* file;
|
||||||
#ifdef MICROCHIP_MPLAB_HARMONY
|
#ifdef MICROCHIP_MPLAB_HARMONY
|
||||||
WFILE f = WBADFILE;
|
WFILE f = WBADFILE;
|
||||||
#endif
|
#endif
|
||||||
byte* in;
|
byte* in;
|
||||||
word32 inSz;
|
word32 inSz;
|
||||||
int format;
|
|
||||||
int ret;
|
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
|
#ifdef MICROCHIP_MPLAB_HARMONY
|
||||||
file = &f;
|
file = &f;
|
||||||
ret = WFOPEN(NULL, &file, name, WOLFSSH_O_RDONLY);
|
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);
|
ret = (int)WFREAD(NULL, in, 1, inSz, file);
|
||||||
|
WFCLOSE(NULL, file);
|
||||||
|
|
||||||
if (ret <= 0 || (word32)ret != inSz) {
|
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
|
if (WSTRNSTR((const char*)in, "ssh-rsa", inSz) == (const char*)in
|
||||||
|| WSTRNSTR((const char*)in,
|
|| WSTRNSTR((const char*)in,
|
||||||
"ecdsa-sha2-nistp", inSz) == (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) {
|
"x509v3-ssh-mldsa-", inSz) == (const char*)in) {
|
||||||
*isPrivate = 0;
|
*isPrivate = 0;
|
||||||
format = WOLFSSH_FORMAT_SSH;
|
format = WOLFSSH_FORMAT_SSH;
|
||||||
in[inSz] = 0;
|
|
||||||
}
|
}
|
||||||
else if (WSTRNSTR((const char*)in, PrivBeginOpenSSH, inSz) != NULL) {
|
else if (WSTRNSTR((const char*)in, PrivBeginOpenSSH, inSz) != NULL) {
|
||||||
*isPrivate = 1;
|
*isPrivate = 1;
|
||||||
|
|
@ -2464,15 +2730,55 @@ int wolfSSH_ReadKey_file(const char* name,
|
||||||
|
|
||||||
ret = wolfSSH_ReadKey_buffer_ex(in, inSz, format,
|
ret = wolfSSH_ReadKey_buffer_ex(in, inSz, format,
|
||||||
out, outSz, outType, outTypeSz, *isPrivate, heap);
|
out, outSz, outType, outTypeSz, *isPrivate, heap);
|
||||||
}
|
|
||||||
|
|
||||||
WFCLOSE(NULL, file);
|
WS_FORCEZERO(in, inSz);
|
||||||
WS_FORCEZERO(in, inSz);
|
WFREE(in, heap, DYNTYPE_FILE);
|
||||||
WFREE(in, heap, DYNTYPE_FILE);
|
}
|
||||||
|
|
||||||
return ret;
|
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
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2930,6 +3236,81 @@ int wolfSSH_CTX_AddRootCert_buffer(WOLFSSH_CTX* ctx,
|
||||||
return ret;
|
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 */
|
#endif /* WOLFSSH_CERTS */
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
492
tests/api.c
492
tests/api.c
|
|
@ -53,6 +53,12 @@
|
||||||
#endif
|
#endif
|
||||||
#ifdef WOLFSSH_OSSH_CERTS
|
#ifdef WOLFSSH_OSSH_CERTS
|
||||||
#include <wolfssh/ossh.h>
|
#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
|
#endif
|
||||||
|
|
||||||
#if defined(WOLFSSH_SFTP) || defined(WOLFSSH_SCP)
|
#if defined(WOLFSSH_SFTP) || defined(WOLFSSH_SCP)
|
||||||
|
|
@ -686,8 +692,10 @@ static void test_wolfSSH_CTX_UseCert_buffer(void)
|
||||||
AssertIntEQ(WS_BAD_ARGUMENT,
|
AssertIntEQ(WS_BAD_ARGUMENT,
|
||||||
wolfSSH_CTX_UseCert_buffer(ctx, NULL, 0, WOLFSSH_FORMAT_PEM));
|
wolfSSH_CTX_UseCert_buffer(ctx, NULL, 0, WOLFSSH_FORMAT_PEM));
|
||||||
|
|
||||||
|
#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP256
|
||||||
AssertIntEQ(WS_SUCCESS,
|
AssertIntEQ(WS_SUCCESS,
|
||||||
wolfSSH_CTX_UseCert_buffer(ctx, cert, certSz, WOLFSSH_FORMAT_PEM));
|
wolfSSH_CTX_UseCert_buffer(ctx, cert, certSz, WOLFSSH_FORMAT_PEM));
|
||||||
|
#endif
|
||||||
|
|
||||||
AssertIntEQ(WS_BAD_FILETYPE_E,
|
AssertIntEQ(WS_BAD_FILETYPE_E,
|
||||||
wolfSSH_CTX_UseCert_buffer(ctx, cert, certSz, WOLFSSH_FORMAT_ASN1));
|
wolfSSH_CTX_UseCert_buffer(ctx, cert, certSz, WOLFSSH_FORMAT_ASN1));
|
||||||
|
|
@ -703,8 +711,10 @@ static void test_wolfSSH_CTX_UseCert_buffer(void)
|
||||||
AssertNotNull(cert);
|
AssertNotNull(cert);
|
||||||
AssertIntNE(0, certSz);
|
AssertIntNE(0, certSz);
|
||||||
|
|
||||||
|
#ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP256
|
||||||
AssertIntEQ(WS_SUCCESS,
|
AssertIntEQ(WS_SUCCESS,
|
||||||
wolfSSH_CTX_UseCert_buffer(ctx, cert, certSz, WOLFSSH_FORMAT_ASN1));
|
wolfSSH_CTX_UseCert_buffer(ctx, cert, certSz, WOLFSSH_FORMAT_ASN1));
|
||||||
|
#endif
|
||||||
|
|
||||||
wolfSSH_CTX_free(ctx);
|
wolfSSH_CTX_free(ctx);
|
||||||
free(cert);
|
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)
|
static void test_wolfSSH_CTX_UsePrivateKey_buffer_pem(void)
|
||||||
{
|
{
|
||||||
#if defined(WOLFSSH_CERTS) && !defined(WOLFSSH_NO_SERVER)
|
#if defined(WOLFSSH_CERTS) && !defined(WOLFSSH_NO_SERVER)
|
||||||
|
|
@ -2204,6 +2572,124 @@ static const byte ossh_vec_ecc[] = {
|
||||||
};
|
};
|
||||||
#endif /* WOLFSSH_NO_ECDSA_SHA2_NISTP256 */
|
#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
|
/* Parse, verify the CA signature, and validate the options of each committed
|
||||||
* certificate vector; then flip the final signature byte and confirm the
|
* certificate vector; then flip the final signature byte and confirm the
|
||||||
* verification fails while the parse still succeeds. */
|
* verification fails while the parse still succeeds. */
|
||||||
|
|
@ -5773,6 +6259,10 @@ int wolfSSH_ApiTest(int argc, char** argv)
|
||||||
test_wolfSSH_ConvertConsole();
|
test_wolfSSH_ConvertConsole();
|
||||||
test_wolfSSH_CTX_UsePrivateKey_buffer();
|
test_wolfSSH_CTX_UsePrivateKey_buffer();
|
||||||
test_wolfSSH_CTX_UseCert_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_UsePrivateKey_buffer_pem();
|
||||||
test_wolfSSH_CTX_SetWindowPacketSize();
|
test_wolfSSH_CTX_SetWindowPacketSize();
|
||||||
test_wolfSSH_CertMan();
|
test_wolfSSH_CertMan();
|
||||||
|
|
@ -5801,6 +6291,8 @@ int wolfSSH_ApiTest(int argc, char** argv)
|
||||||
#endif
|
#endif
|
||||||
#ifdef WOLFSSH_OSSH_CERTS
|
#ifdef WOLFSSH_OSSH_CERTS
|
||||||
#ifndef WOLFSSH_NO_ED25519
|
#ifndef WOLFSSH_NO_ED25519
|
||||||
|
test_wolfSSH_ReadCert_buffer_ossh();
|
||||||
|
test_wolfSSH_ReadCert_file_ossh();
|
||||||
test_wolfSSH_OsshCert_valid();
|
test_wolfSSH_OsshCert_valid();
|
||||||
#ifdef WOLFSSH_TEST_OSSH_VEC_ECC
|
#ifdef WOLFSSH_TEST_OSSH_VEC_ECC
|
||||||
test_wolfSSH_OsshCert_ecc_curve_mismatch();
|
test_wolfSSH_OsshCert_ecc_curve_mismatch();
|
||||||
|
|
|
||||||
|
|
@ -1328,6 +1328,9 @@ WOLFSSH_LOCAL int GetOpenSshKey(WS_KeySignature *key,
|
||||||
WOLFSSH_LOCAL int GetOpenSshPublicKey(WS_KeySignature *key,
|
WOLFSSH_LOCAL int GetOpenSshPublicKey(WS_KeySignature *key,
|
||||||
const byte* buf, word32 len, word32* idx);
|
const byte* buf, word32 len, word32* idx);
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef WOLFSSH_CERTS
|
||||||
|
WOLFSSH_LOCAL int IdentifyCert(const byte* in, word32 inSz, void* heap);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
/* Parsing functions */
|
/* Parsing functions */
|
||||||
|
|
|
||||||
|
|
@ -104,6 +104,19 @@ WOLFSSH_API int wolfSSH_ReadKey_file(const char* name,
|
||||||
byte** out, word32* outSz, const byte** outType, word32* outTypeSz,
|
byte** out, word32* outSz, const byte** outType, word32* outTypeSz,
|
||||||
byte* isPrivate, void* heap);
|
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
|
/* 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.
|
* 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
|
* 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);
|
const byte* cert, word32 certSz, int format);
|
||||||
WOLFSSH_API int wolfSSH_CTX_AddRootCert_buffer(WOLFSSH_CTX* ctx,
|
WOLFSSH_API int wolfSSH_CTX_AddRootCert_buffer(WOLFSSH_CTX* ctx,
|
||||||
const byte* cert, word32 certSz, int format);
|
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 */
|
#endif /* WOLFSSH_CERTS */
|
||||||
WOLFSSH_API int wolfSSH_CTX_SetWindowPacketSize(WOLFSSH_CTX* ctx,
|
WOLFSSH_API int wolfSSH_CTX_SetWindowPacketSize(WOLFSSH_CTX* ctx,
|
||||||
word32 windowSz, word32 maxPacketSz);
|
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 */
|
/* bit map */
|
||||||
#define WOLFSSH_USERAUTH_PASSWORD 0x01
|
#define WOLFSSH_USERAUTH_PASSWORD 0x01
|
||||||
#define WOLFSSH_USERAUTH_PUBLICKEY 0x02
|
#define WOLFSSH_USERAUTH_PUBLICKEY 0x02
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue