diff --git a/src/internal.c b/src/internal.c index 3b305d0d3..6d52faee3 100644 --- a/src/internal.c +++ b/src/internal.c @@ -10661,8 +10661,21 @@ static int DoUserAuthRequestRsa(WOLFSSH* ssh, WS_UserAuthData_PublicKey* pk, } if (ret == WS_SUCCESS) { - encDigestSz = wc_EncodeSignature(encDigest, - digest, digestSz, wc_HashGetOID(hashId)); + int hashOid = wc_HashGetOID(hashId); + + /* wc_EncodeSignature() encodes a bad OID without complaint. */ + encDigestSz = 0; + if (hashOid > 0) { + encDigestSz = wc_EncodeSignature(encDigest, + digest, digestSz, hashOid); + } + if (encDigestSz == 0) { + WLOG(WS_LOG_DEBUG, "Could not encode the digest"); + ret = WS_CRYPTO_FAILED; + } + } + + if (ret == WS_SUCCESS) { ret = wolfSSH_RsaVerify(sig, sigSz, encDigest, encDigestSz, key, ssh->ctx->heap, "DoUserAuthRequestRsa"); } @@ -10821,8 +10834,21 @@ static int DoUserAuthRequestRsaCert(WOLFSSH* ssh, WS_UserAuthData_PublicKey* pk, } if (ret == WS_SUCCESS) { - encDigestSz = wc_EncodeSignature(encDigest, - digest, digestSz, wc_HashGetOID(hashId)); + int hashOid = wc_HashGetOID(hashId); + + /* wc_EncodeSignature() encodes a bad OID without complaint. */ + encDigestSz = 0; + if (hashOid > 0) { + encDigestSz = wc_EncodeSignature(encDigest, + digest, digestSz, hashOid); + } + if (encDigestSz == 0) { + WLOG(WS_LOG_DEBUG, "Could not encode the digest"); + ret = WS_CRYPTO_FAILED; + } + } + + if (ret == WS_SUCCESS) { ret = wolfSSH_RsaVerify(sig, sigSz, encDigest, encDigestSz, key, ssh->ctx->heap, "DoUserAuthRequestRsaCert"); } @@ -11110,16 +11136,16 @@ static int DoUserAuthRequestEccCert(WOLFSSH* ssh, WS_UserAuthData_PublicKey* pk, ret = wc_EccPublicKeyDecode(pub, &idx, key_ptr, pubSz); } + if (ret != 0 && ret != WS_MEMORY_E) { + WLOG(WS_LOG_DEBUG, "Could not decode public key"); + ret = WS_CRYPTO_FAILED; + } + if (pub != NULL) WFREE(pub, ssh->ctx->heap, DYNTYPE_PUBKEY); wc_FreeDecodedCert(&cert); } - if (ret != 0) { - WLOG(WS_LOG_DEBUG, "Could not decode public key"); - ret = WS_CRYPTO_FAILED; - } - /* Bind the certificate's key to the declared algorithm so one credential * cannot authenticate under multiple x509v3-ecdsa-* names, matching the * ParseECCPubKeyCert() binding on the host-key path. */ @@ -11150,10 +11176,29 @@ static int DoUserAuthRequestEccCert(WOLFSSH* ssh, WS_UserAuthData_PublicKey* pk, ret = GetSize(&publicKeyTypeSz, pk->signature, pk->signatureSz, &i); } + /* RFC 6187 Section 3.4: the signature is named for the underlying + * ecdsa-sha2 algorithm; the declared x509v3 name is also accepted. */ if (ret == WS_SUCCESS) { + byte declaredId; + byte sigId; + publicKeyType = pk->signature + i; i += publicKeyTypeSz; - WOLFSSH_UNUSED(publicKeyType); + + declaredId = NameToId((const char*)pk->publicKeyType, + pk->publicKeyTypeSz); + sigId = NameToId((const char*)publicKeyType, publicKeyTypeSz); + if (sigId != declaredId + && !(declaredId == ID_X509V3_ECDSA_SHA2_NISTP256 + && sigId == ID_ECDSA_SHA2_NISTP256) + && !(declaredId == ID_X509V3_ECDSA_SHA2_NISTP384 + && sigId == ID_ECDSA_SHA2_NISTP384) + && !(declaredId == ID_X509V3_ECDSA_SHA2_NISTP521 + && sigId == ID_ECDSA_SHA2_NISTP521)) { + WLOG(WS_LOG_DEBUG, + "Signature's type does not match public key type"); + ret = WS_INVALID_ALGO_ID; + } } if (ret == WS_SUCCESS) { @@ -11453,9 +11498,8 @@ static int DoUserAuthRequestEd25519(WOLFSSH* ssh, } if (ret == WS_SUCCESS) { - ret = wc_ed25519_init_ex(key_ptr, ssh->ctx->heap, INVALID_DEVID); - if (ret == 0) { - ret = WS_SUCCESS; + if (wc_ed25519_init_ex(key_ptr, ssh->ctx->heap, INVALID_DEVID) != 0) { + ret = WS_CRYPTO_FAILED; } } @@ -11480,12 +11524,10 @@ static int DoUserAuthRequestEd25519(WOLFSSH* ssh, } if (ret == WS_SUCCESS) { - ret = wc_ed25519_import_public(pk->publicKey + i, qSz, key_ptr); - } - - if (ret != 0) { - WLOG(WS_LOG_DEBUG, "Could not decode public key"); - ret = WS_CRYPTO_FAILED; + if (wc_ed25519_import_public(pk->publicKey + i, qSz, key_ptr) != 0) { + WLOG(WS_LOG_DEBUG, "Could not decode public key"); + ret = WS_CRYPTO_FAILED; + } } if (ret == WS_SUCCESS) { @@ -26987,6 +27029,14 @@ int wolfSSH_TestParseECCPubKeyCert(WOLFSSH* ssh, byte* pubKey, word32 pubKeySz) return ret; } + +int wolfSSH_TestDoUserAuthRequestEccCert(WOLFSSH* ssh, + WS_UserAuthData_PublicKey* pk, int hashId, byte* digest, + word32 digestSz) +{ + return DoUserAuthRequestEccCert(ssh, pk, (enum wc_HashType)hashId, + digest, digestSz); +} #endif /* WOLFSSH_CERTS */ #endif /* !WOLFSSH_NO_ECDSA */ diff --git a/tests/unit.c b/tests/unit.c index 4f078dd6a..4343e7ef7 100644 --- a/tests/unit.c +++ b/tests/unit.c @@ -15351,6 +15351,28 @@ static int test_DoUserAuthRequestEd25519(void) result = -606; goto done; } + /* A declared type that differs from the blob's is reported as such, not + * as a key decode failure. */ + authData.sf.publicKey.signature = sigBlob; + authData.sf.publicKey.publicKeyType = (const byte*)"ssh-ed448"; + authData.sf.publicKey.publicKeyTypeSz = 9; + ret = wolfSSH_TestDoUserAuthRequestEd25519(ssh, &authData); + authData.sf.publicKey.publicKeyType = (const byte*)keyTypeName; + authData.sf.publicKey.publicKeyTypeSz = keyTypeNameSz; + if (ret != WS_INVALID_ALGO_ID) { + printf("DoUserAuthRequestEd25519 type mismatch: ret=%d\n", ret); + result = -609; goto done; + } + + /* A truncated public key blob is a buffer error. */ + authData.sf.publicKey.publicKeySz = pubKeyBlobSz - 1; + ret = wolfSSH_TestDoUserAuthRequestEd25519(ssh, &authData); + authData.sf.publicKey.publicKeySz = pubKeyBlobSz; + if (ret != WS_BUFFER_E) { + printf("DoUserAuthRequestEd25519 truncated key: ret=%d\n", ret); + result = -610; goto done; + } + done: if (signingKeyInit) wc_ed25519_free(&signingKey); @@ -23091,6 +23113,189 @@ static int test_ParseECCPubKeyCert(void) return result; } + +/* Write r or s as an SSH mpint (RFC 4251 Section 5): a set high bit gets + * a leading zero so the value stays positive. Returns the size written. */ +static word32 eccCertSigMpint(byte* out, const byte* v, word32 vSz) +{ + word32 pad = (vSz > 0 && (v[0] & 0x80)) ? 1 : 0; + + certChainPut32(vSz + pad, out); + if (pad) + out[LENGTH_SZ] = 0; + WMEMCPY(out + LENGTH_SZ + pad, v, vSz); + + return LENGTH_SZ + pad + vSz; +} + + +/* Build an x509v3-ecdsa USERAUTH signature blob, string name || string + * (mpint r || mpint s), into out. Returns its size. */ +static word32 eccCertSigBlob(byte* out, const char* name, const byte* r, + word32 rSz, const byte* s, word32 sSz) +{ + word32 nameSz = (word32)WSTRLEN(name); + word32 idx = 0; + word32 lenIdx; + + certChainPut32(nameSz, out + idx); + idx += LENGTH_SZ; + WMEMCPY(out + idx, name, nameSz); + idx += nameSz; + lenIdx = idx; + idx += LENGTH_SZ; + idx += eccCertSigMpint(out + idx, r, rSz); + idx += eccCertSigMpint(out + idx, s, sSz); + certChainPut32(idx - lenIdx - LENGTH_SZ, out + lenIdx); + + return idx; +} + + +/* DoUserAuthRequestEccCert() Unit Test: signs a digest with keys/fred-key.der + * and checks the signature's algorithm name against the declared + * x509v3-ecdsa-sha2-nistp256 (keys/fred-cert.der is P-256). The declared name + * and RFC 6187's ecdsa-sha2-nistp256 are accepted; any other name is + * WS_INVALID_ALGO_ID. Argument errors keep their own code. Returns 1 to skip + * when the key files are not readable. */ +static int test_DoUserAuthRequestEccCert(void) +{ + static const struct { + const char* name; + int expected; + } names[] = { + { "x509v3-ecdsa-sha2-nistp256", WS_SUCCESS }, + { "ecdsa-sha2-nistp256", WS_SUCCESS }, + { "ecdsa-sha2-nistp384", WS_INVALID_ALGO_ID }, + { "x509v3-ecdsa-sha2-nistp384", WS_INVALID_ALGO_ID }, + { "ssh-rsa", WS_INVALID_ALGO_ID }, + { "", WS_INVALID_ALGO_ID }, + }; + static const char algoName[] = "x509v3-ecdsa-sha2-nistp256"; + WOLFSSH_CTX* ctx = NULL; + WOLFSSH* ssh = NULL; + byte* cert = NULL; + byte* keyDer = NULL; + word32 certSz = 0, keyDerSz = 0, idx = 0; + ecc_key key; + WC_RNG rng; + int keyInit = 0, rngInit = 0; + byte digest[WC_SHA256_DIGEST_SIZE]; + byte der[ECC_MAX_SIG_SIZE]; + word32 derSz = (word32)sizeof(der); + byte r[MAX_ECC_BYTES], s[MAX_ECC_BYTES]; + word32 rSz = (word32)sizeof(r), sSz = (word32)sizeof(s); + byte sigBlob[64 + ECC_MAX_SIG_SIZE]; + WS_UserAuthData_PublicKey pk; + int result = 0; + int ret; + word32 i; + + if (certmanLoadFile("./keys/fred-cert.der", &cert, &certSz) != 0 || + certmanLoadFile("./keys/fred-key.der", &keyDer, &keyDerSz) + != 0) { + printf("DoUserAuthRequestEccCert: SKIP, ./keys not readable\n"); + free(cert); + free(keyDer); + return 1; + } + + WMEMSET(digest, 0x5A, sizeof(digest)); + if (wc_InitRng(&rng) != 0) + result = -1; + else + rngInit = 1; + if (result == 0) { + if (wc_ecc_init(&key) != 0) + result = -2; + else + keyInit = 1; + } + if (result == 0 && wc_EccPrivateKeyDecode(keyDer, &idx, &key, + keyDerSz) != 0) + result = -3; + if (result == 0 && wc_ecc_sign_hash(digest, sizeof(digest), der, &derSz, + &rng, &key) != 0) + result = -4; + if (result == 0 && wc_ecc_sig_to_rs(der, derSz, r, &rSz, s, &sSz) != 0) + result = -5; + + if (result == 0) { + ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); + if (ctx != NULL) + ssh = wolfSSH_new(ctx); + if (ssh == NULL) + result = -6; + } + + WMEMSET(&pk, 0, sizeof(pk)); + pk.publicKeyType = (const byte*)algoName; + pk.publicKeyTypeSz = (word32)WSTRLEN(algoName); + pk.publicKey = cert; + pk.publicKeySz = certSz; + pk.hasSignature = 1; + pk.signature = sigBlob; + + for (i = 0; ssh != NULL && i < sizeof(names) / sizeof(names[0]); i++) { + pk.signatureSz = eccCertSigBlob(sigBlob, names[i].name, + r, rSz, s, sSz); + ret = wolfSSH_TestDoUserAuthRequestEccCert(ssh, &pk, + WC_HASH_TYPE_SHA256, digest, (word32)sizeof(digest)); + if (ret != names[i].expected) { + printf("DoUserAuthRequestEccCert: sig name \"%s\" ret %d, " + "expected %d\n", names[i].name, ret, names[i].expected); + result = -7; + } + } + + /* a signature over another digest fails the verify */ + if (result == 0) { + pk.signatureSz = eccCertSigBlob(sigBlob, algoName, r, rSz, s, sSz); + digest[0] ^= 0x01; + ret = wolfSSH_TestDoUserAuthRequestEccCert(ssh, &pk, + WC_HASH_TYPE_SHA256, digest, (word32)sizeof(digest)); + digest[0] ^= 0x01; + if (ret != WS_ECC_E) { + printf("DoUserAuthRequestEccCert: wrong digest ret %d\n", ret); + result = -8; + } + } + + /* an argument error is not reported as a key decode failure */ + if (result == 0) { + ret = wolfSSH_TestDoUserAuthRequestEccCert(ssh, &pk, + WC_HASH_TYPE_SHA256, digest, 0); + if (ret != WS_BAD_ARGUMENT) { + printf("DoUserAuthRequestEccCert: digestSz 0 ret %d\n", ret); + result = -9; + } + } + + /* a certificate that does not parse is a key decode failure */ + if (result == 0) { + pk.publicKeySz = 16; + ret = wolfSSH_TestDoUserAuthRequestEccCert(ssh, &pk, + WC_HASH_TYPE_SHA256, digest, (word32)sizeof(digest)); + pk.publicKeySz = certSz; + if (ret != WS_CRYPTO_FAILED) { + printf("DoUserAuthRequestEccCert: bad cert ret %d\n", ret); + result = -10; + } + } + + if (keyInit) + wc_ecc_free(&key); + if (rngInit) + wc_FreeRng(&rng); + /* cert and keyDer come from certmanLoadFile()'s malloc */ + free(cert); + free(keyDer); + wolfSSH_free(ssh); + wolfSSH_CTX_free(ctx); + + return result; +} + #endif /* WOLFSSH_CERTS && !NO_FILESYSTEM */ #endif /* !WOLFSSH_NO_ECDSA_SHA2_NISTP256 */ @@ -23283,6 +23488,35 @@ static int test_DoUserAuthRequestRsa(void) } } + /* A hash wc_EncodeSignature() cannot encode is a crypto failure, not a + * bad signature. */ + { + byte sigBlob[sizeof(userAuthRsaSigBlob)]; + byte pubBlob[sizeof(userAuthRsaPubKeyBlob)]; + byte digest[sizeof(userAuthRsaDigest)]; + WS_UserAuthData_PublicKey pk; + + WMEMCPY(sigBlob, userAuthRsaSigBlob, sizeof(sigBlob)); + WMEMCPY(pubBlob, userAuthRsaPubKeyBlob, sizeof(pubBlob)); + WMEMCPY(digest, userAuthRsaDigest, sizeof(digest)); + WMEMSET(&pk, 0, sizeof(pk)); + pk.publicKeyType = (const byte*)"ssh-rsa"; + pk.publicKeyTypeSz = 7; + pk.publicKey = pubBlob; + pk.publicKeySz = (word32)sizeof(pubBlob); + pk.hasSignature = 1; + pk.signature = sigBlob; + pk.signatureSz = (word32)sizeof(sigBlob); + + ret = wolfSSH_TestDoUserAuthRequestRsa(ssh, &pk, WC_HASH_TYPE_NONE, + digest, (word32)sizeof(digest)); + if (ret != WS_CRYPTO_FAILED) { + fprintf(stderr, "\t\"unencodable hash\" FAIL: got %d, " + "expected %d\n", ret, WS_CRYPTO_FAILED); + failures++; + } + } + wolfSSH_free(ssh); wolfSSH_CTX_free(ctx); @@ -23516,6 +23750,33 @@ static int test_DoUserAuthRequestRsaCert(void) } } + /* A hash wc_EncodeSignature() cannot encode is a crypto failure, not a + * bad signature. */ + { + byte sigBlob[sizeof(userAuthRsaSigBlob)]; + byte digest[sizeof(userAuthRsaDigest)]; + WS_UserAuthData_PublicKey pk; + + WMEMCPY(sigBlob, userAuthRsaSigBlob, sizeof(sigBlob)); + WMEMCPY(digest, userAuthRsaDigest, sizeof(digest)); + WMEMSET(&pk, 0, sizeof(pk)); + pk.publicKeyType = (const byte*)"x509v3-ssh-rsa"; + pk.publicKeyTypeSz = 14; + pk.publicKey = userAuthRsaCertDer; + pk.publicKeySz = (word32)sizeof(userAuthRsaCertDer); + pk.hasSignature = 1; + pk.signature = sigBlob; + pk.signatureSz = (word32)sizeof(sigBlob); + + ret = wolfSSH_TestDoUserAuthRequestRsaCert(ssh, &pk, + WC_HASH_TYPE_NONE, digest, (word32)sizeof(digest)); + if (ret != WS_CRYPTO_FAILED) { + fprintf(stderr, "\t\"unencodable hash\" FAIL: got %d, " + "expected %d\n", ret, WS_CRYPTO_FAILED); + failures++; + } + } + wolfSSH_free(ssh); wolfSSH_CTX_free(ctx); @@ -26646,6 +26907,11 @@ int wolfSSH_UnitTest(int argc, char** argv) printf("ParseECCPubKeyCert: %s\n", (unitResult == 0 ? "SUCCESS" : unitResult > 0 ? "SKIPPED" : "FAILED")); testResult = testResult || (unitResult < 0); + + unitResult = test_DoUserAuthRequestEccCert(); + printf("DoUserAuthRequestEccCert: %s\n", (unitResult == 0 ? "SUCCESS" : + unitResult > 0 ? "SKIPPED" : "FAILED")); + testResult = testResult || (unitResult < 0); #endif #endif #if !defined(WOLFSSH_NO_ED25519) && defined(HAVE_ED25519) && \ diff --git a/wolfssh/internal.h b/wolfssh/internal.h index d9c550995..6137f15b4 100644 --- a/wolfssh/internal.h +++ b/wolfssh/internal.h @@ -2251,6 +2251,9 @@ enum WS_MessageIdLimits { #ifdef WOLFSSH_CERTS WOLFSSH_API int wolfSSH_TestParseECCPubKeyCert(WOLFSSH* ssh, byte* pubKey, word32 pubKeySz); + WOLFSSH_API int wolfSSH_TestDoUserAuthRequestEccCert(WOLFSSH* ssh, + WS_UserAuthData_PublicKey* pk, int hashId, byte* digest, + word32 digestSz); #endif /* WOLFSSH_CERTS */ #endif /* !WOLFSSH_NO_ECDSA */ #ifndef WOLFSSH_NO_ED25519