The RFC7518 has a list of algorithms values used in JWT. However there is no value for EdDSA, such as Ed25519. Also Ed25519 is not accepted as a valid value when verifying in Jose. What is the correct alg value for Ed25519?
ED25519 is a EdDSA (Edwards-curve DSA) signature scheme. See also RFC8037 and RFC8032.
According to the jose documentation alg needs to be set to EdDSA:
JWS Algorithm: Edwards-curve DSA
alg: EdDSA
EdDSAis also listed in the section JSON Web Signature and Encryption Algorithms of the IANA Registry (thanks #Florent Morselli for the hint)
Here I show an example how to generate a ed25519 keypair and a signed token in Node.js with jose and crypto and then verify the token with the public key in Python:
Generate key pair and token:
const { SignJWT } = require('jose/jwt/sign')
const { generateKeyPairSync } = require('crypto')
const { publicKey, privateKey } = generateKeyPairSync('ed25519');
console.log(publicKey.export({format:'pem',type:'spki'}))
console.log(privateKey.export({format:'pem',type:'pkcs8'}))
const jwt = await new SignJWT({ 'id': 1 })
.setProtectedHeader({ alg: 'EdDSA' })
.setExpirationTime('2h')
.sign(privateKey)
console.log(jwt)
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEA7fySb/9h7hVH8j1paD5IoLfXj4prjfNLwOPUYKvsTOc=
-----END PUBLIC KEY-----
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIIJtJBnTuKbIy5YjoNiH95ky3DcA3kRB0I2i7DkVM6Cf
-----END PRIVATE KEY-----
eyJhbGciOiJFZERTQSJ9.eyJpZCI6MX0.RAxBAQPFOxrCfgqb56eaAz9u2lByj-WEO-
JWgJH3Cyx1o1Hwjn1pA2M4NgJeob9vb2Oaw4FOeYFr6_33XMTnAQ
the decoded token header:
{
"alg": "EdDSA"
}
Verify the token in Python with PyJWT:
import jwt
public_key = """-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEA7fySb/9h7hVH8j1paD5IoLfXj4prjfNLwOPUYKvsTOc=
-----END PUBLIC KEY-----"""
token = 'eyJhbGciOiJFZERTQSJ9.eyJpZCI6MX0.RAxBAQPFOxrCfgqb56eaAz9u2lByj-WEO-JWgJH3Cyx1o1Hwjn1pA2M4NgJeob9vb2Oaw4FOeYFr6_33XMTnAQ'
decoded = jwt.decode(token, public_key, algorithms='EdDSA', verify=True)
print(decoded)
Note: jwt.io currently doesn't support the EdDSA/ED25519 algorithm, so you can't verify the token on that site. I also don't know about any other JWT website that can verify EdDSA signed tokens.
Related
I am using Scala to generate JWT using RS256 algorithm and private keys:
val jwtPayload = s"""{
| "exp": $time,
| "iss": "$orgId",
| "sub": "$technicalAccountId",
| "aud": "${imsExp}/c/${clientId}",
| "${imsExp}/s/${metaScope}": true
|}""".stripMargin
println(jwtPayload)
val token = Jwts.builder()
.setPayload(jwtPayload)
.signWith(SignatureAlgorithm.RS256,privateKey.getBytes("UTF-8"))
But this fails with the error:
Key bytes may only be specified for HMAC signatures. If using RSA or Elliptic Curve, use the signWith(SignatureAlgorithm, Key) method instead.
at io.jsonwebtoken.lang.Assert.isTrue(Assert.java:38)
But the same code works well in javascript:
const jwtPayload = {
exp: Math.round(300 + Date.now() / 1000),
iss: secrets.org,
sub: secrets.id,
aud: `${secrets.imsEndpoint}/c/${secrets.technicalAccount.clientId}`,
[`${secrets.imsEndpoint}/s/${secrets.metascopes}`]: true
};
let token;
try {
token = jwt.sign(
jwtPayload,
{ key: secrets.privateKey},
{ algorithm: 'RS256' }
);
console.log(token);
} catch (tokenError) {
return Promise.reject(tokenError);
}
I am unable to identify two things:
How to pass passphrase?
How to get rid of below error:
Key bytes may only be specified for HMAC signatures. If using RSA or Elliptic Curve, use the signWith(SignatureAlgorithm, Key) method instead.
at io.jsonwebtoken.lang.Assert.isTrue(Assert.java:38)
When I remove .getBytes method, I recieve a new error:
Exception in thread "main" java.lang.IllegalArgumentException: Base64-encoded key bytes may only be specified for HMAC signatures. If using RSA or Elliptic Curve, use the signWith(SignatureAlgorithm, Key) method instead.
I have a quarkus app which does not generate jwt tokens itself but possesses a secret key of HS256-signed tokens (qwertyuiopasdfghjklzxcvbnm123456). I need to verify tokens of the incoming network requests, but for every request I get the error:
io.smallrye.jwt.auth.principal.ParseException: SRJWT07000: Failed to verify a token
...
Caused by: org.jose4j.jwt.consumer.InvalidJwtSignatureException: JWT rejected due to invalid signature. Additional details: [[9] Invalid JWS Signature: JsonWebSignature{"typ":"JWT","alg":"HS256"}->eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2NjczODI2NzIsImV4cCI6MTY5ODkxODY3MiwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6IkpvaG5ueSIsIlN1cm5hbWUiOiJSb2NrZXQiLCJFbWFpbCI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJSb2xlIjpbIk1hbmFnZXIiLCJQcm9qZWN0IEFkbWluaXN0cmF0b3IiXX0.5vBHzbTKjLnAkAIYuA3c50nWV--o9jIWV2i0GZI-aw4]
My application.properties config:
smallrye.jwt.verify.key-format=JWK
smallrye.jwt.verify.key.location=JWTSecret.jwk
smallrye.jwt.verify.algorithm=HS256
quarkus.native.resources.includes=JWTSecret.jwk
JWTSecret.jwk
{
"kty": "oct",
"k": "qwertyuiopasdfghjklzxcvbnm123456",
"alg": "HS256"
}
I tried to verify the signature of the token with jwt.io using secret key above (and it verified the signature just fine), so my guess there's something wrong with my JWK file or application.properties configuration. I also tried RS256 verification algorithm (with public/private pem keys) and it worked fine, but unfortunately I need it to work with HS256.
Below the code, but it should be ok since it works fine with other verification algorithms.
package co.ogram.domain
import org.eclipse.microprofile.jwt.JsonWebToken
import javax.annotation.security.RolesAllowed
import javax.enterprise.inject.Default
import javax.inject.Inject
import javax.ws.rs.*
import javax.ws.rs.core.Context
import javax.ws.rs.core.MediaType
import javax.ws.rs.core.SecurityContext
#Path("/secured")
class TokenSecuredResource {
#Inject
#field:Default
var jwt: JsonWebToken? = null
#GET
#Path("/roles-allowed")
#RolesAllowed("Admin")
#Produces(MediaType.TEXT_PLAIN)
fun helloRolesAllowed(#Context ctx: SecurityContext): String? {
return getResponseString(ctx!!)
}
private fun getResponseString(ctx: SecurityContext): String {
val name: String
name = if (ctx.userPrincipal == null) {
"anonymous"
} else if (ctx.userPrincipal.name != jwt!!.name) {
throw InternalServerErrorException("Principal and JsonWebToken names do not match")
} else {
ctx.userPrincipal.name
}
val type = jwt!!.getClaim<Int>("type")
return String.format(
"hello + %s,"
+ " isHttps: %s,"
+ " authScheme: %s,"
+ " type: %s,"
+ " hasJWT: %s",
name, ctx.isSecure, ctx.authenticationScheme, type, hasJwt()
)
}
private fun hasJwt(): Boolean {
return jwt!!.claimNames != null
}
}
The jose4j package does the correct verification given the JWK as an input.
Your JWT is signed with the actual octets of jwk.k ("qwertyuiopasdfghjklzxcvbnm123456").
In reality you should base64url decode the k to get a buffer to use as the HS256 secret to sign. This will align with what the jose4j package does (which is correct).
I'm trying to reproduce a decoding of a JWE starting from jwt.io as an example and translating into code by using library jose4j
From site jwt.io I have the following:
HEADER:
{
"alg": "HS256"
}
PAYLOAD:
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
VERIFY SIGNATURE:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
Fdh9u8rINxfivbrianbbVT1u232VQBZYKx1HGAGPt2I
)
the secret base64 is not encoded.
Now I try to reproduce the situation with jose4j and then having as a result the same value on the encoded field, which is:
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.jOJ7G4oijaDk9Tr4ntAXczd6PlI4oVvBU0_5cf7oaz4
Then:
Key key = new HmacKey("Fdh9u8rINxfivbrianbbVT1u232VQBZYKx1HGAGPt2I".getBytes(StandardCharsets.UTF_8));
JsonWebEncryption jwe = new JsonWebEncryption();
String payload = Json.createObjectBuilder()
.add("sub", "1234567890")
.add("name", "John Doe")
.add("iat", "1516239022")
.build()
.toString();
jwe.setPayload(payload);
jwe.setHeader("alg", "HS256");
jwe.setKey(key);
String serializedJwe = jwe.getCompactSerialization();
System.out.println("Serialized Encrypted JWE: " + serializedJwe);
However I get this error:
org.jose4j.lang.InvalidAlgorithmException: HS256 is an unknown, unsupported or unavailable alg algorithm (not one of [RSA1_5, RSA-OAEP, RSA-OAEP-256, dir, A128KW, A192KW, A256KW, ECDH-ES, ECDH-ES+A128KW, ECDH-ES+A192KW, ECDH-ES+A256KW, PBES2-HS256+A128KW, PBES2-HS384+A192KW, PBES2-HS512+A256KW, A128GCMKW, A192GCMKW, A256GCMKW]).
HS256 is a JWS algorithm so you'd need to use JsonWebSignature rather than JsonWebEncryption to accomplish what it looks like you're trying to do.
In my ongoing struggle with public key crypto i ran into a snag that's possibly beyond my expertise with parsing CSR's:
I have not been able to get phpseclib to X509->loadCSR() this chunk from a MS server 2012r2 CA:
# more file
-----BEGIN NEW CERTIFICATE REQUEST-----
MIIFGDCCAwACAQAwOjEWMBQGCgmSJomT8ixkARkWBnNlY3VyZTEgMB4GA1UEAxMX
LlNlY3VyZSBFbnRlcnByaXNlIENBIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
ggIKAoICAQCzgEpL+Za7a3y7YpURDrxlGIBlks25fD0tHaZIYkBTaXA5h+9MWoXn
FA7AlIUt8pbBvXdJbOCmGaeQmBfBH0Qy9vTbx/DR2IOwzqy2ZHuurI5bPL12ceE2
Mxa9xgY/i7U6MAUtoA3amEd7cKj2fz9EWZruRladOX0DXv9KexSan+45QjCWH+u2
Cxem2zH9ZDNPGBuAF9YsAvkdHdAoX8aSm05ZAjUiO2e/+L57whh7zZiDY3WIhin7
N/2JNTKVO6lx50S8a34XUKBt3SKgSR941hcLrBYUNftUYsTPo40bzKKcWqemiH+w
jQiDrln4V2b5EbVeoGWe4UDPXCVmC6UPklG7iYfF0eeK4ujV8uc9PtV2LvGLOFdm
AYE3+FAba5byQATw/DY8EJKQ7ptPigJhVe47NNeJlsKwk1haJ9k8ZazjS+vT45B5
pqe0yBFAEon8TFnOLnAOblmKO12i0zqMUNAAlmr1c8jNjLr+dhruS+QropZmzZ24
mAnFG+Y0qpfhMzAxTGQyVjyGwDfRK/ARmtrGpmROjj5+6VuMmZ6Ljf3xN09epmtH
gJe+lYNBlpfUYg16tm+OusnziYnXL6nIo2ChOY/7GNJJif9fjvvaPDCC98K64av5
5rpIx7N/XH4hwHeQQkEQangExE+8UMyBNFNmvPnIHVHUZdYo4SLsYwIDAQABoIGY
MBsGCisGAQQBgjcNAgMxDRYLNi4zLjk2MDAuMi4weQYJKoZIhvcNAQkOMWwwajAQ
BgkrBgEEAYI3FQEEAwIBADAdBgNVHQ4EFgQU5nEIMEUT5mMd1WepmviwgK7dIzww
GQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB
/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBAKZl6bAeaID3b/ic4aztL8ZZI7vi
D3A9otUKx6v1Xe63zDPR+DiWSnxb9m+l8OPtnWkcLkzEIM/IMWorHKUAJ/J871D0
Qx+0/HbkcrjMtVu/dNrtb9Z9CXup66ZvxTPcpEziq0/n2yw8QdBaa+lli65Qcwcy
tzMQK6WQTRYfvVCIX9AKcPKxwx1DLH+7hL/bERB1lUDu59Jx6fQfqJrFVOY2N8c0
MGvurfoHGmEoyCMIyvmIMu4+/wSNEE/sSDp4lZ6zuF6rf1m0GiLdTX2XJE+gfvep
JTFmp4S3WFqkszKvaxBIT+jV0XKTNDwnO+dpExwU4jZUh18CdEFkIUuQb0gFF8B7
WJFVpNdsRqZRPBz83BW1Kjo0yAmaoTrGNmG0p6Qf3K2zbk1+Jik3VZq4rvKoTi20
6RvLA2//cMNfkYPsuqvoHGe2e0GOLtIB63wJzloWROpb72ohEHsvCKullIJVSuiS
9sfTBAenHCyndgAEd4T3npTUdaiNumVEm5ilZId7LAYekJhkgFu3vlcl8blBJKjE
skVTp7JpBmdXCL/G/6H2SFjca4JMOAy3DxwlGdgneIaXazHs5nBK/BgKPIyPzZ4w
secxBTTCNgI48YezK3GDkn65cmlnkt6F6Mf0MwoDaXTuB88Jycbwb5ihKnHEJIsO
draiRBZruwMPwPIP
-----END NEW CERTIFICATE REQUEST-----
# openssl req -in file -noout -text
Certificate Request:
Data:
Version: 0 (0x0)
Subject: DC=secure, CN=.Secure Enterprise CA 1
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (4096 bit)
Modulus:
00:b3:80:4a:4b:f9:96:bb:6b:7c:bb:62:95:11:0e:
bc:65:18:80:65:92:cd:b9:7c:3d:2d:1d:a6:48:62:
40:53:69:70:39:87:ef:4c:5a:85:e7:14:0e:c0:94:
85:2d:f2:96:c1:bd:77:49:6c:e0:a6:19:a7:90:98:
17:c1:1f:44:32:f6:f4:db:c7:f0:d1:d8:83:b0:ce:
ac:b6:64:7b:ae:ac:8e:5b:3c:bd:76:71:e1:36:33:
16:bd:c6:06:3f:8b:b5:3a:30:05:2d:a0:0d:da:98:
47:7b:70:a8:f6:7f:3f:44:59:9a:ee:46:56:9d:39:
7d:03:5e:ff:4a:7b:14:9a:9f:ee:39:42:30:96:1f:
eb:b6:0b:17:a6:db:31:fd:64:33:4f:18:1b:80:17:
d6:2c:02:f9:1d:1d:d0:28:5f:c6:92:9b:4e:59:02:
35:22:3b:67:bf:f8:be:7b:c2:18:7b:cd:98:83:63:
75:88:86:29:fb:37:fd:89:35:32:95:3b:a9:71:e7:
44:bc:6b:7e:17:50:a0:6d:dd:22:a0:49:1f:78:d6:
17:0b:ac:16:14:35:fb:54:62:c4:cf:a3:8d:1b:cc:
a2:9c:5a:a7:a6:88:7f:b0:8d:08:83:ae:59:f8:57:
66:f9:11:b5:5e:a0:65:9e:e1:40:cf:5c:25:66:0b:
a5:0f:92:51:bb:89:87:c5:d1:e7:8a:e2:e8:d5:f2:
e7:3d:3e:d5:76:2e:f1:8b:38:57:66:01:81:37:f8:
50:1b:6b:96:f2:40:04:f0:fc:36:3c:10:92:90:ee:
9b:4f:8a:02:61:55:ee:3b:34:d7:89:96:c2:b0:93:
58:5a:27:d9:3c:65:ac:e3:4b:eb:d3:e3:90:79:a6:
a7:b4:c8:11:40:12:89:fc:4c:59:ce:2e:70:0e:6e:
59:8a:3b:5d:a2:d3:3a:8c:50:d0:00:96:6a:f5:73:
c8:cd:8c:ba:fe:76:1a:ee:4b:e4:2b:a2:96:66:cd:
9d:b8:98:09:c5:1b:e6:34:aa:97:e1:33:30:31:4c:
64:32:56:3c:86:c0:37:d1:2b:f0:11:9a:da:c6:a6:
64:4e:8e:3e:7e:e9:5b:8c:99:9e:8b:8d:fd:f1:37:
4f:5e:a6:6b:47:80:97:be:95:83:41:96:97:d4:62:
0d:7a:b6:6f:8e:ba:c9:f3:89:89:d7:2f:a9:c8:a3:
60:a1:39:8f:fb:18:d2:49:89:ff:5f:8e:fb:da:3c:
30:82:f7:c2:ba:e1:ab:f9:e6:ba:48:c7:b3:7f:5c:
7e:21:c0:77:90:42:41:10:6a:78:04:c4:4f:bc:50:
cc:81:34:53:66:bc:f9:c8:1d:51:d4:65:d6:28:e1:
22:ec:63
Exponent: 65537 (0x10001)
Attributes:
1.3.6.1.4.1.311.13.2.3 :6.3.9600.2.
Requested Extensions:
1.3.6.1.4.1.311.21.1:
...
X509v3 Subject Key Identifier:
E6:71:08:30:45:13:E6:63:1D:D5:67:A9:9A:F8:B0:80:AE:DD:23:3C
1.3.6.1.4.1.311.20.2: ..S.u.b.C.A
X509v3 Key Usage:
Digital Signature, Certificate Sign, CRL Sign
X509v3 Basic Constraints: critical
CA:TRUE
Signature Algorithm: sha256WithRSAEncryption
a6:65:e9:b0:1e:68:80:f7:6f:f8:9c:e1:ac:ed:2f:c6:59:23:
bb:e2:0f:70:3d:a2:d5:0a:c7:ab:f5:5d:ee:b7:cc:33:d1:f8:
38:96:4a:7c:5b:f6:6f:a5:f0:e3:ed:9d:69:1c:2e:4c:c4:20:
cf:c8:31:6a:2b:1c:a5:00:27:f2:7c:ef:50:f4:43:1f:b4:fc:
76:e4:72:b8:cc:b5:5b:bf:74:da:ed:6f:d6:7d:09:7b:a9:eb:
a6:6f:c5:33:dc:a4:4c:e2:ab:4f:e7:db:2c:3c:41:d0:5a:6b:
e9:65:8b:ae:50:73:07:32:b7:33:10:2b:a5:90:4d:16:1f:bd:
50:88:5f:d0:0a:70:f2:b1:c3:1d:43:2c:7f:bb:84:bf:db:11:
10:75:95:40:ee:e7:d2:71:e9:f4:1f:a8:9a:c5:54:e6:36:37:
c7:34:30:6b:ee:ad:fa:07:1a:61:28:c8:23:08:ca:f9:88:32:
ee:3e:ff:04:8d:10:4f:ec:48:3a:78:95:9e:b3:b8:5e:ab:7f:
59:b4:1a:22:dd:4d:7d:97:24:4f:a0:7e:f7:a9:25:31:66:a7:
84:b7:58:5a:a4:b3:32:af:6b:10:48:4f:e8:d5:d1:72:93:34:
3c:27:3b:e7:69:13:1c:14:e2:36:54:87:5f:02:74:41:64:21:
4b:90:6f:48:05:17:c0:7b:58:91:55:a4:d7:6c:46:a6:51:3c:
1c:fc:dc:15:b5:2a:3a:34:c8:09:9a:a1:3a:c6:36:61:b4:a7:
a4:1f:dc:ad:b3:6e:4d:7e:26:29:37:55:9a:b8:ae:f2:a8:4e:
2d:b4:e9:1b:cb:03:6f:ff:70:c3:5f:91:83:ec:ba:ab:e8:1c:
67:b6:7b:41:8e:2e:d2:01:eb:7c:09:ce:5a:16:44:ea:5b:ef:
6a:21:10:7b:2f:08:ab:a5:94:82:55:4a:e8:92:f6:c7:d3:04:
07:a7:1c:2c:a7:76:00:04:77:84:f7:9e:94:d4:75:a8:8d:ba:
65:44:9b:98:a5:64:87:7b:2c:06:1e:90:98:64:80:5b:b7:be:
57:25:f1:b9:41:24:a8:c4:b2:45:53:a7:b2:69:06:67:57:08:
bf:c6:ff:a1:f6:48:58:dc:6b:82:4c:38:0c:b7:0f:1c:25:19:
d8:27:78:86:97:6b:31:ec:e6:70:4a:fc:18:0a:3c:8c:8f:cd:
9e:30:b1:e7:31:05:34:c2:36:02:38:f1:87:b3:2b:71:83:92:
7e:b9:72:69:67:92:de:85:e8:c7:f4:33:0a:03:69:74:ee:07:
cf:09:c9:c6:f0:6f:98:a1:2a:71:c4:24:8b:0e:76:b6:a2:44:
16:6b:bb:03:0f:c0:f2:0f
I added the mystery vendor specific OID's to the OID list in hopes that would get me somewhere:
// the following are X.509 extensions not supported by phpseclib
'1.3.6.1.5.5.7.1.12' => 'id-pe-logotype',
'1.3.6.1.4.1.311.13.2.3' => 'szOID_OS_VERSION',
'1.3.6.1.4.1.311.20.2' => 'szOID_ENROLL_CERTTYPE_EXTENSION',
'1.3.6.1.4.1.311.21.1' => 'szOID_CERTSRV_CA_VERSION',
But I am unable to get past this part of the loadCSR() function:
$asn1->loadOIDs($this->oids);
$decoded = $asn1->decodeBER($csr);
//... $decoded contains content, but turns into binary junk in the middle
$csr = $asn1->asn1map($decoded[0], $this->CertificationRequest);
if (!isset($csr) || $csr === false) {
Apparently the asn1map function does not like the garbage in the middle of $decoded??? Not sure if there is some kind of way to go about debugging this I am missing, but I dont know enough about BER decoding and asn1 mapping to correct the issue on my own =(
Pretty please help, I really love this library (i use it for so many great things) and would like to use it to authorize enterprise CA's from my offline Linux Root CA with a really awesome php web frontend ;D
The problem is that phpseclib does not appear to support the "Requested Extensions" block that openssl req is showing. A quick Google search suggests that CSR's are governed by PKCS10 and that the "Requested Extensions" block is governed by PKCS9. I'll contact the author and see what can be done about addressing this.
In the mean time, a quick workaround is this:
#
#-----[ OPEN ]------------------------------------------
#
File/ASN1.php
#
#-----[ FIND ]------------------------------------------
#
return $i < $n? null: $map;
#
#-----[ REPLACE WITH ]----------------------------------
#
return $map;
Is it possible to set x509 v3 extended attribute for subjectAltName on a CSR?
I am able to successfully generate a CSR, and then pass that to a CA to sign.
The CA is able to call functions like X509->setDomain("bob.com","*.bob.com","asdf.org"); and they appear in the final cert without issue (note: i am doing the sign, reload, set extensions, resign workaround for phpseclib).
The CSR process calling the same functions $X509->setExtension("id-ce-subjectAltName",array("names","here") ) or $X509->setDomain("domain1","domain2"); does not appear to set some X509 extended attributes in the CSR itself (openssl can), I have only been able to add SAN by the CA after re-import before the second signing.
Thanks!
Edit: I created an example CSR (sign, reload, x509v3 extensions, resign workaround) and it seems possible to set SOME extended attributes (CA: false, key usage, etc.) but NOT the subjectAltName info:
-----BEGIN CERTIFICATE REQUEST-----
MIIC1TCCAb8CAQAwVDELMAkGA1UEBgwCU1MxEDAOBgNVBAgMB09ic2N1cmUxDzAN
BgNVBAcMBlNlY3VyZTEQMA4GA1UECgwHYm9iY29ycDEQMA4GA1UEAwwHYm9iLmNv
bTCCASAwCwYJKoZIhvcNAQEBA4IBDwAwggEKAoIBAQCunNrjxEOILsESZ1osUkT3
zSeAHlzNiCBQnc/Xf+oW7Ir7wKfbHkV10cM583mw3Zy8rlCT/lUq0H+f3Uoc5/FA
dsYWhatlJRlTjv+yjSsxyB9i/hZ/KliP3Ix2O+Pq1wZIWfvk40hmCHHSB6YDtqt0
vXqUTIhH3SfyLtK9nd/6WG8bgIq9jgL6xvF3h44ynEwkuOnHt4a9WrflGX4KKcwo
OM98M/TAntDgSXBEYoLxenIbl3ypa7gtghVHHls3QOSay5QM87K3PJ3kSWVlZ3tN
tZGfbFdLiS+3MH8G3ujSX8XYeBnUj5jXi4SzpzQ9o5pyArL8DD8kC/Q9P/el1aZb
AgMBAAGgQDA+BgkqhkiG9w0BCQ4xMTAvMAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/
BAwwCgYIKwYBBQUHAwEwBwYDVR0RBAAwCwYJKoZIhvcNAQELA4IBAQAUIhgLf6LH
WwhncU+yrNy6yHPHjFbipehUaS/Wa6FBbANpLyEdslqNwFD2FyXofSQVCB0L6VnH
NNXHcfZqWOT8+Xd0yNmfr/w+vg+s1yY4S0VAG6mxGsqwdIJqgXm2jaw0rMpzRs/k
wfOpJ+gyRTrQz9VJrn4xC4Uv6zTGNj56qZehDievW74SGISbGzj7AGmCxpp5/qZD
LN4Ls3wZ7I4TfuYZDh7qWuFwEAEEv40JPF2qO3VitvyAYrKg3bnUlFzQrOsfs4Ot
xzihLwkFEobih1bOEhzTnSv+lMckfw0DQ5Eb8mtFTC+/KOqEMMU5Hq1fm3B9Bkgs
FmqQZk4QlerY
-----END CERTIFICATE REQUEST-----
I figured it out, the SignCSR function in phpseclib doesnt respsect the setDomain function, or any content in the $this->domains array however it IS possible to manually encode and force set the subject alt names yourself:
$OPTIONS["altnames"] = array(
"bob.com",
"10.123.123.123",
"*.bob.com",
"asdf.fdsa",
);
$ALTNAMES = $this->altnames($OPTIONS["altnames"]);
if ( count($ALTNAMES) )
{
$CSR->setExtension("id-ce-subjectAltName" , $ALTNAMES );
}
protected function altnames($ALTNAMES)
{
//Sort names and IPs into two different arrays
$DOMAINS = array(); $IPS = array();
foreach ($ALTNAMES as $ALTNAME)
{
if ( filter_var($ALTNAME, FILTER_VALIDATE_IP) )
{
array_push($IPS,$ALTNAME);
}else{
array_push($DOMAINS,$ALTNAME);
}
}
// Create our altname array for the subjectAltName parser
$RETURN = array();
foreach ($DOMAINS as $DOMAIN) { array_push($RETURN, array("dNSName" => $DOMAIN ) ); }
foreach ($IPS as $IP) { array_push($RETURN, array("iPAddress" => $IP ) ); }
return $RETURN;
}
Now, the one caveat I found (that is a feature issue for phpseclib to consider) is the lack of IPv6 support. If you provide a valid IPv6 address in the altnames array (2620:153::1234) it will translate to IP Address: 0.0.0.0 because of the use of IP2LONG and LONG2IP embedded in the X509 code.
While not a showstopper for my use case, it is a minor annoyance and I have experimented with using a custom IP2BIN and BIN2IP function replacing the encode and decode logic for an IP in the library and not met with much success.
Example CSR generated with my code above:
-----BEGIN CERTIFICATE REQUEST-----
MIIC/DCCAeYCAQAwVDELMAkGA1UEBgwCU1MxEDAOBgNVBAgMB09ic2N1cmUxDzAN
BgNVBAcMBlNlY3VyZTEQMA4GA1UECgwHYm9iY29ycDEQMA4GA1UEAwwHYm9iLmNv
bTCCASAwCwYJKoZIhvcNAQEBA4IBDwAwggEKAoIBAQDOGLdjEr2IyW27e08hmRN+
Bcu4uOWAWEvxvY3+5pHdBQQEXhww5YQuEvpmgbKtav0j7aqFPDYNSXTv+aQNe9fq
cP3nZmKAWU+qbQMjWxwV9mEJOlWI214v7C8lLbMvBlny141J7KTvv1TGGLCBH/V2
EnQSdJzGDwXmJ2k0iChlQ7zl1TlonamYX9gefzp3N/DDp6kNhuPSX9zRorYIp5CC
WEIRmDdegwxHACrNu0K4xwuPjTRJf0oUkRsfBuDxqvBalQ0bzd/23fiQ51MEVla3
fUWL/+b2SKOlvgfu0XbfZ+Qx21DeyRQpqIWnv0gR4AM8qltxUgRjZloUfK5IQ8rp
AgMBAAGgZzBlBgkqhkiG9w0BCQ4xWDBWMAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/
BAwwCgYIKwYBBQUHAwEwLgYDVR0RBCcwJYIHYm9iLmNvbYIJKi5ib2IuY29tgglh
c2RmLmZkc2GHBAp7e3swCwYJKoZIhvcNAQELA4IBAQAf1Vlt09ZBhVpxlmi+n93u
Gm8fNrd5afeIzWj0h4dFGJSOg5T8SkfL5txk2C9tQEdayQWB1kllx5rIqXAPe6gz
kbcjJn8l2IB3khIoKYmylmtV8Yo0Fl/Xba1oLCAsixbK+UxiSLgXqMryz9DBy40s
5oYXpy5JOnqL7BRC7b+Lk/chw7CcncPZI4rei6HM8WATymTySdrPoQegvBj0VIar
qHBZrMV9lsjTREJ9hvA/FycA/PNlP9y8N+eTF9SBrnTi8ix+v+Iirc43xeD2EVLG
6uqXecClji6OEOKcdDsH0D0HD1PMFmKB0FWvq71dt7eVIHkTPwTLFG2XAjfn6Fb8
-----END CERTIFICATE REQUEST-----
Decodes to:
Certificate Request:
Data:
Version: 0 (0x0)
Subject: C=SS, ST=Obscure, L=Secure, O=bobcorp, CN=bob.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:ce:18:b7:63:12:bd:88:c9:6d:bb:7b:4f:21:99:
13:7e:05:cb:b8:b8:e5:80:58:4b:f1:bd:8d:fe:e6:
91:dd:05:04:04:5e:1c:30:e5:84:2e:12:fa:66:81:
b2:ad:6a:fd:23:ed:aa:85:3c:36:0d:49:74:ef:f9:
a4:0d:7b:d7:ea:70:fd:e7:66:62:80:59:4f:aa:6d:
03:23:5b:1c:15:f6:61:09:3a:55:88:db:5e:2f:ec:
2f:25:2d:b3:2f:06:59:f2:d7:8d:49:ec:a4:ef:bf:
54:c6:18:b0:81:1f:f5:76:12:74:12:74:9c:c6:0f:
05:e6:27:69:34:88:28:65:43:bc:e5:d5:39:68:9d:
a9:98:5f:d8:1e:7f:3a:77:37:f0:c3:a7:a9:0d:86:
e3:d2:5f:dc:d1:a2:b6:08:a7:90:82:58:42:11:98:
37:5e:83:0c:47:00:2a:cd:bb:42:b8:c7:0b:8f:8d:
34:49:7f:4a:14:91:1b:1f:06:e0:f1:aa:f0:5a:95:
0d:1b:cd:df:f6:dd:f8:90:e7:53:04:56:56:b7:7d:
45:8b:ff:e6:f6:48:a3:a5:be:07:ee:d1:76:df:67:
e4:31:db:50:de:c9:14:29:a8:85:a7:bf:48:11:e0:
03:3c:aa:5b:71:52:04:63:66:5a:14:7c:ae:48:43:
ca:e9
Exponent: 65537 (0x10001)
Attributes:
Requested Extensions:
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Extended Key Usage: critical
TLS Web Server Authentication
X509v3 Subject Alternative Name:
DNS:bob.com, DNS:*.bob.com, DNS:asdf.fdsa, IP Address:10.123.123.123
Signature Algorithm: sha256WithRSAEncryption
1f:d5:59:6d:d3:d6:41:85:5a:71:96:68:be:9f:dd:ee:1a:6f:
1f:36:b7:79:69:f7:88:cd:68:f4:87:87:45:18:94:8e:83:94:
fc:4a:47:cb:e6:dc:64:d8:2f:6d:40:47:5a:c9:05:81:d6:49:
65:c7:9a:c8:a9:70:0f:7b:a8:33:91:b7:23:26:7f:25:d8:80:
77:92:12:28:29:89:b2:96:6b:55:f1:8a:34:16:5f:d7:6d:ad:
68:2c:20:2c:8b:16:ca:f9:4c:62:48:b8:17:a8:ca:f2:cf:d0:
c1:cb:8d:2c:e6:86:17:a7:2e:49:3a:7a:8b:ec:14:42:ed:bf:
8b:93:f7:21:c3:b0:9c:9d:c3:d9:23:8a:de:8b:a1:cc:f1:60:
13:ca:64:f2:49:da:cf:a1:07:a0:bc:18:f4:54:86:ab:a8:70:
59:ac:c5:7d:96:c8:d3:44:42:7d:86:f0:3f:17:27:00:fc:f3:
65:3f:dc:bc:37:e7:93:17:d4:81:ae:74:e2:f2:2c:7e:bf:e2:
22:ad:ce:37:c5:e0:f6:11:52:c6:ea:ea:97:79:c0:a5:8e:2e:
8e:10:e2:9c:74:3b:07:d0:3d:07:0f:53:cc:16:62:81:d0:55:
af:ab:bd:5d:b7:b7:95:20:79:13:3f:04:cb:14:6d:97:02:37:
e7:e8:56:fc
OK, I think i found a "feature" in phpseclib's X509 signing function:
function sign($issuer, $subject, $signatureAlgorithm = 'sha1WithRSAEncryption')
If you sign the CSR, the code in the sign function claims to copy the X509 v3 extended attributes, however by the time it executes this line:
$csrexts = $subject->getAttribute('pkcs-9-at-extensionRequest', 0);
The result is always blank (an empty array), the attributes in $THIS (new) cert were overwritten somewhere in the previous ~30 lines of code.
I moved the csrexts line to the top of the Sign function, and attributes are now being copied correctly into generated certificates with X509 extended attributes in the CSR (that had to be manually calculated and set by my previous answer)
I will use sourceforge to try and post a bug request to save others the struggle hopefully.
OK, so I've got this to work using an example from the PHPSecLib documentation and playing around with it for a good few hours. Perhaps this is something added post February, but in any case, here is how to generate a CSR with an Subject Alternative Name:
$privKey = new Crypt_RSA();
extract($privKey->createKey());
$privKey->loadKey($privatekey);
$x509 = new File_X509();
$x509->setPrivateKey($privKey);
$x509->setDNProp('id-at-organizationName', 'Company');
$x509->setDNProp('id-at-organizationalUnitName', 'CompanyDepartment');
$x509->setDNProp('id-at-commonName', 'myurl.com');
$x509->setDNProp('id-at-localityName', $this->application);
$x509->loadCSR($x509->saveCSR($x509->signCSR()));
// Set extension request.
$x509->setExtension("id-ce-subjectAltName", [
['dNSName' => 'myalternativename.com', 'iPAddress' => 127.0.0.1]
]);
echo $x509->saveCSR($x509->signCSR());
That should print out the CSR with myalternativename.com as the SAN.
The bug still actual in 2020.
Cause of this bug is need for a valid $x509->currentCert array. The setExtension() function has no effect with empty currentCert array.
The currentCert array structure is equal to result of signCSR() function. Therefore you can just initialize it as following: $x509->currentCert = $x509->signCSR();
Now you can call setExtension() function.
Here is full example how to generate CSR with SAN attribute:
use \phpseclib\Crypt\RSA;
use \phpseclib\File\X509;
$privateKey = new RSA();
$privateKey->loadKey(file_get_contents(__DIR__ . '/private.key'));
$x509 = new X509();
$x509->setPrivateKey($privateKey);
$x509->setDNProp('commonname', 'example.com');
$x509->setDNProp('emailaddress', 'admin#mail.com');
$x509->currentCert = $x509->signCSR(); // Important!
$x509->setExtension('id-ce-subjectAltName', [
['iPAddress' => '127.0.0.1'],
['dNSName' => 'www.example.com'],
]);
$csr = $x509->signCSR();
file_put_contents(__DIR__ . '/domain.csr', $x509->saveCSR($csr));
echo "OK\n";