/***** BEGIN LICENSE BLOCK *****
* Version: EPL 1.0/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Eclipse Public
* License Version 1.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.eclipse.org/legal/epl-v10.html
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* Copyright (C) 2013 Matt Hauck <matthauck@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the EPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the EPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****/
package org.jruby.ext.openssl.impl;
import java.util.List;
import java.io.OutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.KeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.DSAPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.util.Enumeration;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1Set;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.DLSequence;
import org.bouncycastle.asn1.pkcs.CertificationRequestInfo;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.pkcs.CertificationRequest;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.pkcs.Attribute;
import org.bouncycastle.crypto.util.PublicKeyFactory;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.params.DSAPublicKeyParameters;
import org.bouncycastle.crypto.params.DSAParameters;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.RSAKeyParameters;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.ContentVerifier;
import org.bouncycastle.operator.ContentVerifierProvider;
import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
import org.bouncycastle.pkcs.PKCSException;
import org.jruby.ext.openssl.SecurityHelper;
public class PKCS10Request {
private X500Name subject;
private SubjectPublicKeyInfo publicKeyInfo;
private List<Attribute> attributes;
private transient PKCS10CertificationRequest signedRequest;
public PKCS10Request(X500Name subject,
SubjectPublicKeyInfo publicKeyInfo, List<Attribute> attrs) {
this.subject = subject;
this.publicKeyInfo = publicKeyInfo;
this.attributes = attrs;
}
public PKCS10Request(X500Name subject,
PublicKey publicKey, List<Attribute> attrs) {
this.subject = subject;
this.publicKeyInfo = makePublicKeyInfo(publicKey);
this.attributes = attrs;
}
// For reading existing requests
public PKCS10Request(final CertificationRequest req) {
subject = req.getCertificationRequestInfo().getSubject();
publicKeyInfo = req.getCertificationRequestInfo().getSubjectPublicKeyInfo();
setAttributes( req.getCertificationRequestInfo().getAttributes() );
signedRequest = new PKCS10CertificationRequest(req); // valid = true;
}
public PKCS10Request(byte[] bytes) {
this(CertificationRequest.getInstance(bytes));
}
public PKCS10Request(ASN1Sequence sequence) {
this(CertificationRequest.getInstance(sequence));
}
private void resetSignedRequest() {
if ( signedRequest == null ) return;
CertificationRequest req = signedRequest.toASN1Structure();
CertificationRequestInfo reqInfo = new CertificationRequestInfo(subject, publicKeyInfo, req.getCertificationRequestInfo().getAttributes());
ASN1Sequence seq = (ASN1Sequence) req.toASN1Primitive();
req = new CertificationRequest(reqInfo, (AlgorithmIdentifier) seq.getObjectAt(1), (DERBitString) seq.getObjectAt(2));
signedRequest = new PKCS10CertificationRequest(req); // valid = true;
}
// sign
public PKCS10CertificationRequest sign(final PrivateKey privateKey,
final AlgorithmIdentifier signatureAlg)
throws NoSuchAlgorithmException, InvalidKeyException {
final ContentSigner signer = new PKCS10Signer(privateKey, signatureAlg);
signedRequest = newBuilder().build(signer); // valid = true;
return signedRequest;
}
public PKCS10CertificationRequest sign(final PrivateKey privateKey,
final String digestAlg)
throws NoSuchAlgorithmException, InvalidKeyException {
String sigAlg = digestAlg + "WITH" + getPublicKeyAlgorithm();
return sign( privateKey,
new DefaultSignatureAlgorithmIdentifierFinder().find( sigAlg )
);
}
// verify
public boolean verify(final PublicKey publicKey) throws InvalidKeyException {
if ( signedRequest == null ) {
if ( true ) throw new IllegalStateException("no signed request");
return false;
}
try {
ContentVerifierProvider verifier = new PKCS10VerifierProvider( publicKey );
return signedRequest.isSignatureValid( verifier );
}
catch (PKCSException e) {
throw new InvalidKeyException(e);
}
}
// privates
private PKCS10CertificationRequestBuilder newBuilder() {
final PKCS10CertificationRequestBuilder builder =
new PKCS10CertificationRequestBuilder(subject, publicKeyInfo);
if ( attributes != null ) {
for ( Attribute attribute : attributes ) {
builder.addAttribute(attribute.getAttrType(), attribute.getAttributeValues());
}
}
return builder;
}
private static SubjectPublicKeyInfo makePublicKeyInfo(PublicKey publicKey) {
if ( publicKey == null ) return null;
return SubjectPublicKeyInfo.getInstance( publicKey.getEncoded() );
}
// conversion
public ASN1Sequence toASN1Structure() {
if ( signedRequest == null ) {
// return an empty Sequence
return new DLSequence();
}
return ASN1Sequence.getInstance( signedRequest.toASN1Structure() );
}
// getters and setters
public void setSubject(final X500Name subject) {
this.subject = subject;
resetSignedRequest();
}
public X500Name getSubject() {
return subject;
}
//private transient String publicKeyAlgorithm;
public void setPublicKey(final PublicKey publicKey) {
this.publicKeyInfo = makePublicKeyInfo(publicKey);
//if ( publicKey == null ) publicKeyAlgorithm = null;
//else publicKeyAlgorithm = publicKey.getAlgorithm();
resetSignedRequest();
}
private String getPublicKeyAlgorithm() {
//if ( publicKeyAlgorithm == null ) {
// throw new IllegalStateException("no public key info");
//}
//return publicKeyAlgorithm;
if ( publicKeyInfo == null ) {
throw new IllegalStateException("no public key info");
}
AlgorithmIdentifier algId = publicKeyInfo.getAlgorithm();
return ASN1Registry.oid2sym( algId.getAlgorithm() );
}
public PublicKey generatePublicKey() throws NoSuchAlgorithmException,
InvalidKeySpecException, IOException {
AsymmetricKeyParameter keyParams = PublicKeyFactory.createKey(publicKeyInfo);
final KeySpec keySpec; final KeyFactory keyFactory;
if ( keyParams instanceof RSAKeyParameters ) {
RSAKeyParameters rsa = (RSAKeyParameters) keyParams;
keySpec = new RSAPublicKeySpec(
rsa.getModulus(), rsa.getExponent()
);
keyFactory = SecurityHelper.getKeyFactory("RSA");
return keyFactory.generatePublic(keySpec);
}
else if ( keyParams instanceof DSAPublicKeyParameters ) {
DSAPublicKeyParameters dsa = (DSAPublicKeyParameters) keyParams;
DSAParameters params = dsa.getParameters();
keySpec = new DSAPublicKeySpec(
dsa.getY(), params.getP(), params.getQ(), params.getG()
);
keyFactory = SecurityHelper.getKeyFactory("DSA");
return keyFactory.generatePublic(keySpec);
}
else if ( keyParams instanceof ECPublicKeyParameters ) {
ECPublicKeyParameters ec = (ECPublicKeyParameters) keyParams;
ECDomainParameters ecParams = ec.getParameters();
ECParameterSpec params = new ECParameterSpec(
ecParams.getCurve(),
ecParams.getG(), ecParams.getN(), ecParams.getH(),
ecParams.getSeed()
);
// NOTE: likely to fail if non BC factory picked up :
keySpec = new ECPublicKeySpec(ec.getQ(), params);
keyFactory = SecurityHelper.getKeyFactory("EC");
return keyFactory.generatePublic(keySpec);
}
else {
throw new IllegalStateException("could not generate public key for request, params type: " + keyParams);
}
}
public Attribute[] getAttributes() {
return signedRequest != null ? signedRequest.getAttributes() :
attributes.toArray(new Attribute[ attributes.size() ]);
}
public void setAttributes(final List<Attribute> attrs) {
this.attributes = attrs;
}
private void setAttributes(final ASN1Set attrs) {
this.attributes = new ArrayList<Attribute>();
final Enumeration e = attrs.getObjects();
while ( e.hasMoreElements() ) {
addAttribute( Attribute.getInstance( e.nextElement() ) );
}
}
public void addAttribute(final Attribute attribute) {
this.attributes.add( attribute );
}
public BigInteger getVersion() {
if ( signedRequest == null ) return null;
return signedRequest.toASN1Structure().
getCertificationRequestInfo().
getVersion().getValue();
}
private static class PKCS10Signer implements ContentSigner {
final AlgorithmIdentifier signatureAlg;
final Signature signature;
private final SignatureOutputStream out;
PKCS10Signer(PrivateKey privateKey, AlgorithmIdentifier signatureAlg)
throws NoSuchAlgorithmException, InvalidKeyException {
this.signatureAlg = signatureAlg;
signature = SecurityHelper.getSignature( signatureAlg.getAlgorithm().getId() );
signature.initSign( privateKey );
out = new SignatureOutputStream(signature);
}
public AlgorithmIdentifier getAlgorithmIdentifier() { return signatureAlg; }
public OutputStream getOutputStream() { return out; }
public byte[] getSignature() {
try {
return signature.sign();
}
catch (SignatureException e) {
throw new RuntimeException("Could not read signature: " + e);
}
}
}
private static class PKCS10VerifierProvider implements ContentVerifierProvider {
final PublicKey publicKey;
PKCS10VerifierProvider(PublicKey key) {
publicKey = key;
}
public ContentVerifier get(AlgorithmIdentifier sigAlg) {
try {
return new PKCS10Verifier(publicKey, sigAlg);
}
catch (Exception e) {
throw new RuntimeException("Could not create content verifier: " + e);
}
}
public boolean hasAssociatedCertificate() {
return false;
}
public org.bouncycastle.cert.X509CertificateHolder getAssociatedCertificate() {
return null;
}
}
private static class PKCS10Verifier implements ContentVerifier {
final AlgorithmIdentifier signatureAlg;
final Signature signature;
private final SignatureOutputStream out;
public PKCS10Verifier(PublicKey publicKey, AlgorithmIdentifier signatureAlg)
throws NoSuchAlgorithmException, InvalidKeyException {
this.signatureAlg = signatureAlg;
signature = SecurityHelper.getSignature( signatureAlg.getAlgorithm().getId() );
signature.initVerify( publicKey );
out = new SignatureOutputStream(signature);
}
public AlgorithmIdentifier getAlgorithmIdentifier() { return signatureAlg; }
public OutputStream getOutputStream() { return out; }
public boolean verify(byte[] expected) {
try {
return signature.verify( expected );
}
catch (SignatureException e) {
throw new RuntimeException("Could not verify signature: " + e);
}
}
}
private static class SignatureOutputStream extends OutputStream {
private final Signature signature;
SignatureOutputStream(Signature signature) {
this.signature = signature;
}
@Override
public void write(byte[] bytes, int off, int len) throws IOException {
try {
signature.update(bytes, off, len);
}
catch (SignatureException e) {
throw new IOException(e);
}
}
@Override
public void write(byte[] bytes) throws IOException {
try {
signature.update(bytes);
}
catch (SignatureException e) {
throw new IOException(e);
}
}
@Override
public void write(int b) throws IOException {
try {
signature.update((byte) b);
}
catch (SignatureException e) {
throw new IOException(e);
}
}
}
}