Skip to content

Commit 71c406a

Browse files
authored
Implement logic to remove SAN of specific type (#87)
* Implement and document removal of SAN of specific type * Add modifications to SAN extension for removal of specific type
1 parent feb1fd1 commit 71c406a

File tree

6 files changed

+118
-8
lines changed

6 files changed

+118
-8
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
_This version was not yet released._
88

99
- Add new `Continue` action for Directory Services mapping that will cause the certificate request not getting denied when no object could be found.
10+
- Subject Alternative Names can be removed via an empty `Value` in a `OutboundSubjectRule` the same way as it was already possible for the Subject Distinguished Name.
1011

1112
### Version 1.7.1609.1089
1213

TameMyCerts.Tests/CertificateContentValidatorTests.cs

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ public void Does_transfer_RDN_to_RDN()
407407
}
408408

409409
[Fact]
410-
public void Does_transfer_RDN_to_RDN_and_clears_original_RDN()
410+
public void Does_transfer_RDN_to_RDN_and_removes_original_RDN()
411411
{
412412
var policy = new CertificateRequestPolicy
413413
{
@@ -1317,7 +1317,7 @@ public void Allow_but_dont_add_RDN_if_DS_attribute_too_long()
13171317
}
13181318

13191319
[Fact]
1320-
public void Does_clear_existing_RDN()
1320+
public void Does_remove_existing_RDN()
13211321
{
13221322
var policy = new CertificateRequestPolicy
13231323
{
@@ -1347,7 +1347,62 @@ public void Does_clear_existing_RDN()
13471347
}
13481348

13491349
[Fact]
1350-
public void Does_clear_nonexisting_RDN()
1350+
public void Does_remove_existing_SAN()
1351+
{
1352+
// 2048 Bit RSA Key
1353+
// CN=intranet.adcslabor.de
1354+
// dnsName=intranet.adcslabor.de
1355+
const string request =
1356+
"-----BEGIN NEW CERTIFICATE REQUEST-----\n" +
1357+
"MIIDkjCCAnoCAQAwIDEeMBwGA1UEAxMVaW50cmFuZXQuYWRjc2xhYm9yLmRlMIIB\n" +
1358+
"IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3GmfcSDSunQ6+vmz9mTHcEKg\n" +
1359+
"DMzDSXj0lQ7Erazl9CJ4WzROZaa1BUITfRlVXreku6ljYsO3jyTDBRBtCUXNwFk+\n" +
1360+
"MTmzTqXx82MRpK2ATDp2jEPfP7l7K30DwDyiapkpaAvZlxIVWtIDoGxAG+yRFjAF\n" +
1361+
"Qh4HDvSaBoaNvwdjZsUcdgOuJQbIwBhto/RB+4L23oT7+8e2GyRMm/bQK2gDvCbV\n" +
1362+
"9SwTwm9gXljth0wuZ8RRkC7MMVIiPaxUH575SUKE7YvHeZ4Hq20Q2XYBSigqNXBM\n" +
1363+
"VCUVCfsBGA18/MR/ZMFSSCIt2KLjkpp5q9gOCibw0oPrGTqUoLtCkLREbMrHbQID\n" +
1364+
"AQABoIIBKzAcBgorBgEEAYI3DQIDMQ4WDDEwLjAuMTkwNDQuMjA+BgkrBgEEAYI3\n" +
1365+
"FRQxMTAvAgEFDApvdHRpLW90dGVsDA5PVFRJLU9UVEVMXHV3ZQwOcG93ZXJzaGVs\n" +
1366+
"bC5leGUwYwYJKoZIhvcNAQkOMVYwVDAOBgNVHQ8BAf8EBAMCB4AwIwYDVR0RAQH/\n" +
1367+
"BBkwF4IVaW50cmFuZXQuYWRjc2xhYm9yLmRlMB0GA1UdDgQWBBRmh46ij+b3RODb\n" +
1368+
"JXIj5NFC58DFZzBmBgorBgEEAYI3DQICMVgwVgIBAB5OAE0AaQBjAHIAbwBzAG8A\n" +
1369+
"ZgB0ACAAUwBvAGYAdAB3AGEAcgBlACAASwBlAHkAIABTAHQAbwByAGEAZwBlACAA\n" +
1370+
"UAByAG8AdgBpAGQAZQByAwEAMA0GCSqGSIb3DQEBCwUAA4IBAQAmQ8B9fZ+ewB3+\n" +
1371+
"kDFsJcqeMJ+nbFBcHJKmKfhn9564tiBZayK8kpkTvS1Cjb5C79Yimimw2AqGqdFK\n" +
1372+
"W3+wWPCkFN996GoXFOU+lg3I5Byz3Eq4Vyv/H7RCufC68ezVG5v4EaqE4TsYcfoE\n" +
1373+
"zH8HJu0jKKf+QKj9LpXI+HYLwvQ0Fyz4lr839NMidsPF4AWMpEXs/2OSTjg5qDVj\n" +
1374+
"LKMPzd0wrOea0XWx2fEeibdW+KFi1656J+OIGuYP/q0SaPqYgFey+kOS2KLz+9/r\n" +
1375+
"CA+TvKzFxxgRPAfA0TO7GAuwspV2wLOfXVOxIpG5GkmpxeK0nZvyw9HvxWWNlkgw\n" +
1376+
"kbUQqV43\n" +
1377+
"-----END NEW CERTIFICATE REQUEST-----";
1378+
1379+
var policy = new CertificateRequestPolicy
1380+
{
1381+
OutboundSubjectAlternativeName = new List<OutboundSubjectRule>
1382+
{
1383+
new()
1384+
{
1385+
Field = SanTypes.DnsName,
1386+
Value = string.Empty,
1387+
Force = true
1388+
}
1389+
}
1390+
};
1391+
1392+
var dbRow = new CertificateDatabaseRow(request, CertCli.CR_IN_PKCS10);
1393+
1394+
var result = new CertificateRequestValidationResult(dbRow);
1395+
result = _validator.VerifyRequest(result, policy, dbRow, null, _caConfig);
1396+
1397+
PrintResult(result);
1398+
1399+
Assert.False(result.DeniedForIssuance);
1400+
Assert.Equal(WinError.ERROR_SUCCESS, result.StatusCode);
1401+
Assert.False(result.SubjectAlternativeNameExtension.AlternativeNames.Exists(x => x.Key.Equals(SanTypes.DnsName)));
1402+
}
1403+
1404+
[Fact]
1405+
public void Does_remove_nonexisting_RDN()
13511406
{
13521407
var policy = new CertificateRequestPolicy
13531408
{
@@ -1375,6 +1430,34 @@ public void Does_clear_nonexisting_RDN()
13751430
result.CertificateProperties[RdnTypes.NameProperty[RdnTypes.State]].Equals(string.Empty));
13761431
}
13771432

1433+
[Fact]
1434+
public void Does_remove_nonexisting_SAN()
1435+
{
1436+
var policy = new CertificateRequestPolicy
1437+
{
1438+
OutboundSubjectAlternativeName = new List<OutboundSubjectRule>
1439+
{
1440+
new()
1441+
{
1442+
Field = SanTypes.DnsName,
1443+
Value = string.Empty,
1444+
Force = true
1445+
}
1446+
}
1447+
};
1448+
1449+
var dbRow = new CertificateDatabaseRow(_defaultCsr, CertCli.CR_IN_PKCS10);
1450+
1451+
var result = new CertificateRequestValidationResult(dbRow);
1452+
result = _validator.VerifyRequest(result, policy, dbRow, null, _caConfig);
1453+
1454+
PrintResult(result);
1455+
1456+
Assert.False(result.DeniedForIssuance);
1457+
Assert.Equal(WinError.ERROR_SUCCESS, result.StatusCode);
1458+
Assert.False(result.SubjectAlternativeNameExtension.AlternativeNames.Exists(x => x.Key.Equals(SanTypes.DnsName)));
1459+
}
1460+
13781461
[Fact]
13791462
public void Does_not_clear_existing_RDN_if_not_mandatory()
13801463
{

TameMyCerts/Validators/CertificateContentValidator.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,11 @@ public CertificateRequestValidationResult VerifyRequest(CertificateRequestValida
151151

152152
foreach (var rule in policy.OutboundSubjectAlternativeName)
153153
{
154+
if (rule.Value.Equals(string.Empty))
155+
{
156+
result.SubjectAlternativeNameExtension.RemoveAllAlternativeNames(rule.Field);
157+
}
158+
154159
// Check if the SAN is already present unless it is forced
155160
if (!rule.Force && SanTypes.ToList().Contains(rule.Field) &&
156161
result.SubjectAlternativeNameExtension.AlternativeNames.Any(x =>
@@ -175,7 +180,6 @@ public CertificateRequestValidationResult VerifyRequest(CertificateRequestValida
175180
: dbRow.SubjectRelativeDistinguishedNames);
176181
value = ReplaceTokenValues(value, "san", dbRow.SubjectAlternativeNames);
177182

178-
179183
result.SubjectAlternativeNameExtension.AddAlternativeName(rule.Field, value, true);
180184

181185
// Log that we are adding a SAN, only on success

TameMyCerts/X509/X509CertificateExtensionSubjectAlternativeName.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,16 @@ public bool TryAddAlternativeName(string type, string value)
325325

326326
public bool ContainsAlternativeName(string type, string value)
327327
{
328-
return AlternativeNames.Contains(new KeyValuePair<string, string>(type, value));
328+
return AlternativeNames.Exists(kvp =>
329+
kvp.Key.Equals(type, StringComparison.OrdinalIgnoreCase) &&
330+
kvp.Value.Equals(value, StringComparison.OrdinalIgnoreCase));
331+
}
332+
333+
public void RemoveAllAlternativeNames(string type)
334+
{
335+
AlternativeNames.RemoveAll(kvp =>
336+
kvp.Key.Equals(type, StringComparison.OrdinalIgnoreCase));
337+
_modified = true;
329338
}
330339

331340
public void RemoveAlternativeName(string type, string value)
@@ -335,7 +344,9 @@ public void RemoveAlternativeName(string type, string value)
335344
return;
336345
}
337346

338-
AlternativeNames.Remove(new KeyValuePair<string, string>(type, value));
347+
AlternativeNames.RemoveAll(kvp =>
348+
kvp.Key.Equals(type, StringComparison.OrdinalIgnoreCase) &&
349+
kvp.Value.Equals(value, StringComparison.OrdinalIgnoreCase));
339350
_modified = true;
340351
}
341352
}

user-guide/modify-san.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,16 @@ The `commonName` from the Subject DN is transferred to the Subject Alternative N
6868
</OutboundSubjectRule>
6969
</OutboundSubjectAlternativeName>
7070
```
71+
72+
All Subject Alternative Names of the `dNSName` type will be removed from the issueed certificate, if present in the certificate request.
73+
74+
```xml
75+
<OutboundSubjectAlternativeName>
76+
<OutboundSubjectRule>
77+
<Field>dNSName</Field>
78+
<Value></Value>
79+
<Mandatory>true</Mandatory>
80+
<Force>true</Force>
81+
</OutboundSubjectRule>
82+
</OutboundSubjectAlternativeName>
83+
```

user-guide/modify-subject-dn.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,6 @@ Transfering the **first** `dNSName` of the Subject Alternative Name into the `co
156156
The content of the `stateOrProvinceName` field will be removed from the issueed certificate, if present in the certificate request.
157157

158158
```xml
159-
<DirectoryServicesMapping />
160159
<OutboundSubject>
161160
<OutboundSubjectRule>
162161
<Field>stateOrProvinceName</Field>
@@ -170,7 +169,6 @@ The content of the `stateOrProvinceName` field will be removed from the issueed
170169
The content of the `stateOrProvinceName` field will be transferred from the certificate request into the `serialNumber` field of the issued certificate, and the `stateOrProvinceName` field will be removed from the issued certificate.
171170

172171
```xml
173-
<DirectoryServicesMapping />
174172
<OutboundSubject>
175173
<OutboundSubjectRule>
176174
<Field>stateOrProvinceName</Field>

0 commit comments

Comments
 (0)