전송 모드 사용
다음 샘플 코드에서는 사용자 인증 및 협상 검색과 함께 AuthIP를 사용하여 IPsec 전송 모드를 구성하는 방법을 보여 줍니다.
#include <winsock2.h>
#include <fwpmu.h>
#include <mstcpip.h>
#include <stdio.h>
#pragma comment(lib, "fwpuclnt.lib")
#pragma comment(lib, "crypt32.lib")
#pragma comment(lib, "rpcrt4.lib")
#define EXIT_ON_ERROR(fnName) \
if (result != ERROR_SUCCESS) \
{ \
printf(#fnName " = 0x%08X\n", result); \
goto CLEANUP; \
}
DWORD ConfigureIPsecTransportMode(
__in HANDLE engine,
__in PCWSTR policyName,
__in_opt const GUID* providerKey,
__in PCWSTR rootCertSubject
)
{
DWORD result = ERROR_SUCCESS;
HCERTSTORE certStore = NULL;
PCCERT_CONTEXT rootCert = NULL;
IKEEXT_CERT_ROOT_CONFIG0 certRootConfig;
BOOL txnInProgress = FALSE;
IKEEXT_AUTHENTICATION_METHOD0 mmAuthMethods[2];
IKEEXT_PROPOSAL0 mmProposals[2];
IKEEXT_POLICY0 mmPolicy;
FWPM_PROVIDER_CONTEXT0 provCtxt;
FWPM_FILTER0 filter;
IKEEXT_AUTHENTICATION_METHOD0 emAuthMethods[2];
IKEEXT_EM_POLICY0 emPolicy;
const IPSEC_SA_LIFETIME0 qmLifetime =
{
3600, // lifetimeSeconds
100000, // lifetimeKilobytes
0x7FFFFFFF // lifetimePackets
};
IPSEC_AUTH_TRANSFORM0 qmTransform00;
IPSEC_SA_TRANSFORM0 qmTransforms0[1];
IPSEC_AUTH_TRANSFORM0 qmTransform10;
IPSEC_SA_TRANSFORM0 qmTransforms1[1];
IPSEC_AUTH_AND_CIPHER_TRANSFORM0 qmTransform20;
IPSEC_SA_TRANSFORM0 qmTransforms2[1];
IPSEC_AUTH_AND_CIPHER_TRANSFORM0 qmTransform30;
IPSEC_SA_TRANSFORM0 qmTransforms3[1];
IPSEC_PROPOSAL0 qmProposals[4];
IPSEC_TRANSPORT_POLICY0 qmPolicy;
FWPM_FILTER_CONDITION0 filterConditions[2];
// Open the local machine's CA certificate store.
certStore = CertOpenStore(
CERT_STORE_PROV_SYSTEM,
0,
0,
( CERT_STORE_OPEN_EXISTING_FLAG |
CERT_STORE_READONLY_FLAG |
CERT_SYSTEM_STORE_LOCAL_MACHINE),
L"Root"
);
result = (certStore != NULL) ? ERROR_SUCCESS : GetLastError();
EXIT_ON_ERROR(CertOpenStore);
// Find the specified root certificate.
rootCert = CertFindCertificateInStore(
certStore,
(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING),
0,
CERT_FIND_SUBJECT_STR,
rootCertSubject,
NULL
);
result = (rootCert != NULL) ? ERROR_SUCCESS : GetLastError();
EXIT_ON_ERROR(CertFindCertificateInStore);
// The CERT_INFO.Subject name blob is already in the format expected by
// AuthIP.
memset(&certRootConfig, 0, sizeof(certRootConfig));
certRootConfig.certData.size = rootCert->pCertInfo->Subject.cbData;
certRootConfig.certData.data = rootCert->pCertInfo->Subject.pbData;
// We add all the objects from within a single transaction to make it easy
// to clean up partial results in error paths.
result = FwpmTransactionBegin0(engine, 0);
EXIT_ON_ERROR(FwpmTransactionBegin0);
txnInProgress = TRUE;
memset(mmAuthMethods, 0, sizeof(mmAuthMethods));
// Primary main mode auth method is Kerberos. This is the most efficient for
// domain-joined machines.
mmAuthMethods[0].authenticationMethodType = IKEEXT_KERBEROS;
// Secondary main mode auth method is SSL. This allows non-joined machines
// to authenticate as well provided they have been provisioned with an
// appropriate certificate.
mmAuthMethods[1].authenticationMethodType = IKEEXT_SSL;
mmAuthMethods[1].sslAuthentication.inboundConfigType =
IKEEXT_CERT_CONFIG_EXPLICIT_TRUST_LIST;
mmAuthMethods[1].sslAuthentication.inboundRootArraySize = 1;
mmAuthMethods[1].sslAuthentication.inboundRootArray = &certRootConfig;
mmAuthMethods[1].sslAuthentication.outboundConfigType =
IKEEXT_CERT_CONFIG_EXPLICIT_TRUST_LIST;
mmAuthMethods[1].sslAuthentication.outboundRootArraySize = 1;
mmAuthMethods[1].sslAuthentication.outboundRootArray = &certRootConfig;
// Disable CRL checks for test purposes. Consider enabling them in real use.
mmAuthMethods[1].sslAuthentication.flags =
IKEEXT_CERT_AUTH_FLAG_DISABLE_CRL_CHECK;
// Configure two main mode proposals: AES-128/SHA-1 & 3DES/SHA-1 each with a
// lifetime of 8 hours. Fallback to 3DES might be desirable to allow some
// servers to use 3DES for performance reasons.
memset(mmProposals, 0, sizeof(mmProposals));
mmProposals[0].cipherAlgorithm.algoIdentifier = IKEEXT_CIPHER_AES_128;
mmProposals[0].integrityAlgorithm.algoIdentifier = IKEEXT_INTEGRITY_SHA1;
mmProposals[0].maxLifetimeSeconds = 8 * 60 * 60;
mmProposals[1].cipherAlgorithm.algoIdentifier = IKEEXT_CIPHER_3DES;
mmProposals[1].integrityAlgorithm.algoIdentifier = IKEEXT_INTEGRITY_SHA1;
mmProposals[1].maxLifetimeSeconds = 8 * 60 * 60;
memset(&mmPolicy, 0, sizeof(mmPolicy));
mmPolicy.numAuthenticationMethods = ARRAYSIZE(mmAuthMethods);
mmPolicy.authenticationMethods = mmAuthMethods;
mmPolicy.numIkeProposals = ARRAYSIZE(mmProposals);
mmPolicy.ikeProposals = mmProposals;
memset(&provCtxt, 0, sizeof(provCtxt));
// We have to assign the key ourselves since we will need it when adding the
// filters that reference this provider context.
result = UuidCreate(&(provCtxt.providerContextKey));
EXIT_ON_ERROR(UuidCreate);
// For MUI compatibility, object names should be indirect strings. See
// SHLoadIndirectString for details.
provCtxt.displayData.name = (PWSTR)policyName;
// Link all our objects to our provider. When multiple providers are
// installed on a computer, this makes it easy to determine who added what.
provCtxt.providerKey = (GUID*)providerKey;
provCtxt.type = FWPM_IPSEC_AUTHIP_MM_CONTEXT;
provCtxt.authIpMmPolicy = &mmPolicy;
// Add the main mode policy.
result = FwpmProviderContextAdd0(engine, &provCtxt, NULL, NULL);
EXIT_ON_ERROR(FwpmProviderContextAdd0);
// Add filters referencing the policy to the IKEEXT policy layers.
// We could also add remote address conditions to the filters to
// limit which machines or subnets are protected. Note that we use the same
// main mode policy for both IPv4 and IPv6.
memset(&filter, 0, sizeof(filter));
filter.displayData.name = (PWSTR)policyName;
filter.flags = FWPM_FILTER_FLAG_HAS_PROVIDER_CONTEXT;
filter.providerKey = (GUID*)providerKey;
// Policy filters should always have an action type of PERMIT.
filter.action.type = FWP_ACTION_PERMIT;
filter.providerContextKey = provCtxt.providerContextKey;
filter.layerKey = FWPM_LAYER_IKEEXT_V4;
result = FwpmFilterAdd0(engine, &filter, NULL, NULL);
EXIT_ON_ERROR(FwpmFilterAdd0);
filter.layerKey = FWPM_LAYER_IKEEXT_V6;
result = FwpmFilterAdd0(engine, &filter, NULL, NULL);
EXIT_ON_ERROR(FwpmFilterAdd0);
// Allow two extended mode authentication methods: Kerberos for
// domain-joined machines and NTLMv2 for non-joined machines.
memset(&emAuthMethods, 0, sizeof(emAuthMethods));
emAuthMethods[0].authenticationMethodType = IKEEXT_KERBEROS;
emAuthMethods[1].authenticationMethodType = IKEEXT_NTLM_V2;
memset(&emPolicy, 0, sizeof(emPolicy));
emPolicy.numAuthenticationMethods = ARRAYSIZE(emAuthMethods);
emPolicy.authenticationMethods = emAuthMethods;
emPolicy.initiatorImpersonationType = IKEEXT_IMPERSONATION_SOCKET_PRINCIPAL;
memset(qmProposals, 0, sizeof(qmProposals));
// First QM proposal is AH. This is preferable to ESP auth since
// more routers are capable of parsing AH.
memset(&qmTransform00, 0, sizeof(qmTransform00));
qmTransform00.authTransformId = IPSEC_AUTH_TRANSFORM_ID_HMAC_SHA_1_96;
memset(qmTransforms0, 0, sizeof(qmTransforms0));
qmTransforms0[0].ipsecTransformType = IPSEC_TRANSFORM_AH;
qmTransforms0[0].ahTransform = &qmTransform00;
qmProposals[0].lifetime = qmLifetime;
qmProposals[0].numSaTransforms = ARRAYSIZE(qmTransforms0);
qmProposals[0].saTransforms = qmTransforms0;
// Second QM proposal is ESP auth. This might be necessary for NAT
// traversal.
memset(&qmTransform10, 0, sizeof(qmTransform10));
qmTransform10.authTransformId = IPSEC_AUTH_TRANSFORM_ID_HMAC_SHA_1_96;
memset(&qmTransforms1, 0, sizeof(qmTransforms1));
qmTransforms1[0].ipsecTransformType = IPSEC_TRANSFORM_ESP_AUTH;
qmTransforms1[0].espAuthTranform = &qmTransform10;
qmProposals[1].lifetime = qmLifetime;
qmProposals[1].numSaTransforms = ARRAYSIZE(qmTransforms1);
qmProposals[1].saTransforms = qmTransforms1;
// Third QM proposal is ESP auth and cipher using AES-128. This will
// allow remote peers to require encryption if necessary.
memset(&qmTransform20, 0, sizeof(qmTransform20));
qmTransform20.authTransform.authTransformId =
IPSEC_AUTH_TRANSFORM_ID_HMAC_SHA_1_96;
qmTransform20.cipherTransform.cipherTransformId =
IPSEC_CIPHER_TRANSFORM_ID_AES_128;
memset(&qmTransforms2, 0, sizeof(qmTransforms2));
qmTransforms2[0].ipsecTransformType = IPSEC_TRANSFORM_ESP_AUTH_AND_CIPHER;
qmTransforms2[0].espAuthAndCipherTransform = &qmTransform20;
qmProposals[2].lifetime = qmLifetime;
qmProposals[2].numSaTransforms = ARRAYSIZE(qmTransforms2);
qmProposals[2].saTransforms = qmTransforms2;
// Fourth QM proposal allows fallback to 3DES for perf reasons.
memset(&qmTransform30, 0, sizeof(qmTransform30));
qmTransform30.authTransform.authTransformId =
IPSEC_AUTH_TRANSFORM_ID_HMAC_SHA_1_96;
qmTransform30.cipherTransform.cipherTransformId =
IPSEC_CIPHER_TRANSFORM_ID_CBC_3DES;
memset(&qmTransforms3, 0, sizeof(qmTransforms3));
qmTransforms3[0].ipsecTransformType = IPSEC_TRANSFORM_ESP_AUTH_AND_CIPHER;
qmTransforms3[0].espAuthAndCipherTransform = &qmTransform30;
qmProposals[3].lifetime = qmLifetime;
qmProposals[3].numSaTransforms = ARRAYSIZE(qmTransforms3);
qmProposals[3].saTransforms = qmTransforms3;
memset(&qmPolicy, 0, sizeof(qmPolicy));
qmPolicy.numIpsecProposals = ARRAYSIZE(qmProposals);
qmPolicy.ipsecProposals = qmProposals;
// Set ND_SECURE since we require IPsec for all inbound connections.
// This flag alone doesn't guarantee enforcement and is only used to
// ensure connectivity in IPsec+NAT scenarios.
qmPolicy.flags = IPSEC_POLICY_FLAG_ND_SECURE;
qmPolicy.ndAllowClearTimeoutSeconds = 10;
qmPolicy.saIdleTimeout.idleTimeoutSeconds = 300;
qmPolicy.saIdleTimeout.idleTimeoutSecondsFailOver = 60;
qmPolicy.emPolicy = &emPolicy;
memset(&provCtxt, 0, sizeof(provCtxt));
result = UuidCreate(&(provCtxt.providerContextKey));
EXIT_ON_ERROR(UuidCreate);
provCtxt.displayData.name = (PWSTR)policyName;
provCtxt.providerKey = (GUID*)providerKey;
provCtxt.type = FWPM_IPSEC_AUTHIP_QM_TRANSPORT_CONTEXT;
provCtxt.authipQmTransportPolicy = &qmPolicy;
// Add the main mode policy.
result = FwpmProviderContextAdd0(engine, &provCtxt, NULL, NULL);
EXIT_ON_ERROR(FwpmProviderContextAdd0);
// Add filters referencing the policy to the IPsec policy layers.
// As above, we could also add remote address conditions to the
// filters to limit which machines or subnets are protected.
// Note that we use the same quick mode policy for both IPv4 and IPv6.
memset(&filter, 0, sizeof(filter));
filter.displayData.name = (PWSTR)policyName;
filter.flags = FWPM_FILTER_FLAG_HAS_PROVIDER_CONTEXT;
filter.providerKey = (GUID*)providerKey;
filter.action.type = FWP_ACTION_PERMIT;
filter.providerContextKey = provCtxt.providerContextKey;
filter.layerKey = FWPM_LAYER_IPSEC_V4;
result = FwpmFilterAdd0(engine, &filter, NULL, NULL);
EXIT_ON_ERROR(FwpmFilterAdd0);
filter.layerKey = FWPM_LAYER_IPSEC_V6;
result = FwpmFilterAdd0(engine, &filter, NULL, NULL);
EXIT_ON_ERROR(FwpmFilterAdd0);
// We only protect unicast traffic.
memset(filterConditions, 0, sizeof(filterConditions));
filterConditions[0].fieldKey = FWPM_CONDITION_IP_LOCAL_ADDRESS_TYPE;
filterConditions[0].matchType = FWP_MATCH_EQUAL;
filterConditions[0].conditionValue.type = FWP_UINT8;
filterConditions[0].conditionValue.uint8 = NlatUnicast;
// Fields common to all transport and ALE IPsec callout filters.
memset(&filter, 0, sizeof(filter));
filter.displayData.name = (PWSTR)policyName;
filter.providerKey = (GUID*)providerKey;
filter.action.type = FWP_ACTION_CALLOUT_TERMINATING;
filter.numFilterConditions = 1;
filter.filterCondition = filterConditions;
filter.providerContextKey = provCtxt.providerContextKey;
filter.layerKey = FWPM_LAYER_OUTBOUND_TRANSPORT_V4;
filter.action.calloutKey = FWPM_CALLOUT_IPSEC_OUTBOUND_TRANSPORT_V4;
filter.rawContext = FWPM_CONTEXT_IPSEC_OUTBOUND_NEGOTIATE_DISCOVER;
result = FwpmFilterAdd0(engine, &filter, NULL, NULL);
EXIT_ON_ERROR(FwpmFilterAdd0);
filter.layerKey = FWPM_LAYER_OUTBOUND_TRANSPORT_V6;
filter.action.calloutKey = FWPM_CALLOUT_IPSEC_OUTBOUND_TRANSPORT_V6;
filter.rawContext = FWPM_CONTEXT_IPSEC_OUTBOUND_NEGOTIATE_DISCOVER;
result = FwpmFilterAdd0(engine, &filter, NULL, NULL);
EXIT_ON_ERROR(FwpmFilterAdd0);
filter.layerKey = FWPM_LAYER_INBOUND_TRANSPORT_V4;
filter.action.calloutKey = FWPM_CALLOUT_IPSEC_INBOUND_TRANSPORT_V4;
filter.rawContext = FWPM_CONTEXT_IPSEC_INBOUND_PERSIST_CONNECTION_SECURITY;
result = FwpmFilterAdd0(engine, &filter, NULL, NULL);
EXIT_ON_ERROR(FwpmFilterAdd0);
filter.layerKey = FWPM_LAYER_INBOUND_TRANSPORT_V6;
filter.action.calloutKey = FWPM_CALLOUT_IPSEC_INBOUND_TRANSPORT_V6;
filter.rawContext = FWPM_CONTEXT_IPSEC_INBOUND_PERSIST_CONNECTION_SECURITY;
result = FwpmFilterAdd0(engine, &filter, NULL, NULL);
EXIT_ON_ERROR(FwpmFilterAdd0);
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4;
filter.action.calloutKey = FWPM_CALLOUT_IPSEC_INBOUND_INITIATE_SECURE_V4;
filter.rawContext = FWPM_CONTEXT_IPSEC_INBOUND_PERSIST_CONNECTION_SECURITY;
result = FwpmFilterAdd0(engine, &filter, NULL, NULL);
EXIT_ON_ERROR(FwpmFilterAdd0);
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6;
filter.action.calloutKey = FWPM_CALLOUT_IPSEC_INBOUND_INITIATE_SECURE_V6;
filter.rawContext = FWPM_CONTEXT_IPSEC_INBOUND_PERSIST_CONNECTION_SECURITY;
result = FwpmFilterAdd0(engine, &filter, NULL, NULL);
EXIT_ON_ERROR(FwpmFilterAdd0);
// Next we'll add filters to exempt ICMP traffic.
filterConditions[1].fieldKey = FWPM_CONDITION_IP_PROTOCOL;
filterConditions[1].matchType = FWP_MATCH_EQUAL;
filterConditions[1].conditionValue.type = FWP_UINT8;
filterConditions[1].conditionValue.uint8 = IPPROTO_ICMP;
memset(&filter, 0, sizeof(filter));
filter.displayData.name = (PWSTR)policyName;
filter.providerKey = (GUID*)providerKey;
filter.weight.type = FWP_UINT8;
// The exemptions must be added at a higher weight range to ensure
// that they will match before the IPsec callout filters added above.
filter.weight.uint8 = FWPM_WEIGHT_RANGE_IKE_EXEMPTIONS;
filter.action.type = FWP_ACTION_CALLOUT_TERMINATING;
filter.numFilterConditions = 2;
filter.filterCondition = filterConditions;
filter.action.type = FWP_ACTION_PERMIT;
filter.layerKey = FWPM_LAYER_OUTBOUND_TRANSPORT_V4;
result = FwpmFilterAdd0(engine, &filter, NULL, NULL);
EXIT_ON_ERROR(FwpmFilterAdd0);
filter.layerKey = FWPM_LAYER_INBOUND_TRANSPORT_V4;
result = FwpmFilterAdd0(engine, &filter, NULL, NULL);
EXIT_ON_ERROR(FwpmFilterAdd0);
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4;
result = FwpmFilterAdd0(engine, &filter, NULL, NULL);
EXIT_ON_ERROR(FwpmFilterAdd0);
filterConditions[1].conditionValue.uint8 = IPPROTO_ICMPV6;
filter.layerKey = FWPM_LAYER_OUTBOUND_TRANSPORT_V6;
result = FwpmFilterAdd0(engine, &filter, NULL, NULL);
EXIT_ON_ERROR(FwpmFilterAdd0);
filter.layerKey = FWPM_LAYER_INBOUND_TRANSPORT_V6;
result = FwpmFilterAdd0(engine, &filter, NULL, NULL);
EXIT_ON_ERROR(FwpmFilterAdd0);
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6;
result = FwpmFilterAdd0(engine, &filter, NULL, NULL);
EXIT_ON_ERROR(FwpmFilterAdd0);
// Once all the adds have succeeded, we commit the transaction to
// add all the new objects atomically.
result = FwpmTransactionCommit0(engine);
EXIT_ON_ERROR(FwpmTransactionCommit0);
txnInProgress = FALSE;
CLEANUP:
if (txnInProgress)
{
// Abort any transaction still in progress to clean up
// partial results.
FwpmTransactionAbort0(engine);
}
if (rootCert != NULL)
{
CertFreeCertificateContext(rootCert);
}
if (certStore != NULL)
{
CertCloseStore(certStore, CERT_CLOSE_STORE_CHECK_FLAG);
}
return result;
}